[CF 965E]Short Code
你有\(n\)个两两不同的串,你要将每个串变成其的一个前缀,使得变换后的所有串仍然两两不同,并且所有串长度和尽可能小,输出这个和。
\(n\leq 10^5\),所有串长之和不超过\(10^5\)。
[CF 900F]Unusual Sequence
求有多少正整数序列,满足所有数的最大公约数为\(x\),所有数的和为\(y\)。
\(1\leq x, y\leq 10^9\)。
[CF 438E]The Child and Binary Tree
给你一个大小为\(n\)的集合\({c_1, c_2,\ldots ,c_n}\),规定合法的二叉树为带点权且点权都属于给定集合中的点的数。对于任意整数$i\in [1, m]$,求出有多少不同的点权和为\(i\)的二叉树并输出之。
\(1\leq n, m, c_i\leq 10^5\)。
[CF 1000F]One Occurrence
好前一段时间因为一些神必原因……博客放到了https://yahyachan.github.io。然后因为我回学校了所以接下来很长时间可能会继续用这个博客?
我谔谔,还是说这题……
对于询问\([l, r]\),考虑所有数在其中第一次出现的位置,如果说这个数在整个区间里只出现了一次,那么这个数下一次出现的位置一定大于\(r\)。
因此我们用扫描线搞一下就好啦……
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
using pii = std::pair<int, int>;
const int maxn = 500005;
const int maxno = maxn << 2;
pii maxv[maxno];
void maintain(int o) {
maxv[o] = std::max(maxv[o << 1], maxv[o << 1 | 1]);
}
void build_tree(int o, int L, int R) {
if(L == R) {
maxv[o] = std::make_pair(-1, -1);
} else {
int M = (L + R) / 2;
build_tree(o << 1, L, M);
build_tree(o << 1 | 1, M + 1, R);
maintain(o);
}
}
void modify(int o, int L, int R, const int &p, const pii &v) {
if(L == R) {
maxv[o] = v;
} else {
int M = (L + R) / 2;
if(p <= M) {
modify(o << 1, L, M, p, v);
} else {
modify(o << 1 | 1, M + 1, R, p, v);
}
maintain(o);
}
}
pii query(int o, int L, int R, const int &ql, const int &qr) {
if(ql <= L && R <= qr) {
return maxv[o];
} else {
int M = (L + R) / 2;
pii ret = std::make_pair(-100, -100);
if(ql <= M) ret = std::max(ret, query(o << 1, L, M, ql, qr));
if(qr > M) ret = std::max(ret, query(o << 1 | 1, M + 1, R, ql, qr));
return ret;
}
}
int rec[maxn], next[maxn];
int A[maxn];
std::vector<pii> que[maxn]; int ans[maxn];
int main() {
int n; scanf("%d", &n);
std::fill(rec, rec + maxn, n + 1);
for(int i = 1; i <= n; i ++) scanf("%d", &A[i]);
for(int i = n; i >= 1; i --) {
next[i] = rec[A[i]];
rec[A[i]] = i;
}
int q; scanf("%d", &q);
for(int i = 1; i <= q; i ++) {
int l, r; scanf("%d%d", &l, &r);
que[l].push_back({r, i});
}
build_tree(1, 1, n);
for(int i = 1; i <= 500000; i ++) {
if(rec[i] <= n) {
modify(1, 1, n, rec[i], {next[rec[i]], i});
}
}
for(int i = 1; i <= n; i ++) {
for(const pii &g : que[i]) {
int r = g.first, id = g.second;
pii tmp = query(1, 1, n, i, r);
if(tmp.first <= r) {
ans[id] = 0;
} else {
ans[id] = tmp.second;
}
}
int nx = next[i];
if(nx <= n) {
modify(1, 1, n, nx, {next[nx], A[i]});
}
}
for(int i = 1; i <= q; i ++) {
printf("%d\n", ans[i]);
}
return 0;
}
[CF 618F]Double Knapsack
我zzs就算掉光rating,R2爆炸,也不会做你们半道构造题!
啊构造题真好玩(一转)。
不妨钦定\(A\)中所有元素的和不大于\(B\)的。然后把两个集合按照任意顺序搞成一个序列,然后求出两者的前缀和\(SA\)和\(SB\)。
考虑对于0...n的\(SA_i\),找出\(SB\)中不大于他的数中最大的一个(不妨设为\(SB_j\)),可以发现有\(0\leq SA_i - SB_j\leq n - 1\)(不然\(j\)还能再大)。然后发现:我们处理的\(i\)和\(j\)的数对有\(n + 1\)组,但是\(SA_i - SB_j\)的取值却只有N种!也就是说一定存在\(i\neq i'\)且\(j\neq j'\)满足\(SA_i - SB_j = SA_{i'} - SB_{j'}\)。
我们找出两对这样的数对,移一下项就可以构造一组解了。
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
using ll = long long;
using pii = std::pair<int, int>;
int n;
void gen_S(int *arr, ll *S) {
S[0] = 0LL;
for(int i = 1; i <= n; i ++) {
S[i] = (S[i - 1] + (ll(arr[i])));
}
}
const int maxn = 1000005;
std::vector<pii> cs[maxn];
int main() {
scanf("%d", &n);
int *A = (int*)calloc(n + 1, sizeof(int));
int *B = (int*)calloc(n + 1, sizeof(int));
ll sum_A = 0LL, sum_B = 0LL;
for(int i = 1; i <= n; i ++) {
scanf("%d", &A[i]); sum_A += A[i];
}
for(int i = 1; i <= n; i ++) {
scanf("%d", &B[i]); sum_B += B[i];
}
bool flip = false;
if(sum_A > sum_B) {
flip = true;
std::swap(A, B);
std::swap(sum_A, sum_B);
}
ll *SA = (ll*)calloc(n + 1, sizeof(ll));;
ll *SB = (ll*)calloc(n + 1, sizeof(ll));;
gen_S(A, SA); gen_S(B, SB);
for(int i = 0; i <= n; i ++) {
int j = std::upper_bound(SB, SB + n + 1, SA[i]) - SB - 1;
ll val = SA[i] - SB[j];
cs[val].push_back(std::make_pair(i, j));
}
int l1, r1, l2, r2;
for(int i = 0; i < n; i ++) {
if(cs[i].size() > 1) {
int i1 = cs[i][0].first;
int j1 = cs[i][0].second;
int i2 = cs[i][1].first;
int j2 = cs[i][1].second;
if(i1 > i2) {
std::swap(i1, i2);
std::swap(j1, j2);
}
l1 = i1; r1 = i2;
l2 = j1; r2 = j2;
break;
}
}
if(flip) {
std::swap(l1, l2);
std::swap(r1, r2);
}
printf("%d\n", r1 - l1);
for(int i = l1 + 1; i <= r1; i ++) {
if(i > l1 + 1) putchar(' ');
printf("%d", i);
}
putchar('\n');
printf("%d\n", r2 - l2);
for(int i = l2 + 1; i <= r2; i ++) {
if(i > l2 + 1) putchar(' ');
printf("%d", i);
}
putchar('\n');
free(A); free(B);
free(SA); free(SB);
return 0;
}
[CF 19E]Fairy
啊啊啊我只会做水题了……
这题就是要求你只删除一条边拆爆所有的奇环,同时不能产生新的gay环,求所有可能的选择方案。
如果说只有一个奇环,那么上面的树边和非树边都是可以拆爆的。但如果有好几个奇环,那么只能选择树边了。奇环的树边部分肯定是若干链,我们只要树上差分标记一下就能求出这些链的交了。
但考虑一件事,如果一个偶环和一个奇环的树边部分相交了,那么删掉其中一边会导致新的奇环出现(证明的话考虑将环异或一下)。所以说在偶环的树边并上的边也不能取。
注意这题图可能不连通……所以要对每个连通块都DFS。
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#include <vector>
using ll = long long;
const int maxn = 10005;
const int maxm = 20005;
struct Edge {
int u, v;
int id;
};
std::vector<Edge> G[maxn];
int n, m;
inline void ins_edge(int u, int v, int id) {
G[u].push_back((Edge){u, v, id});
G[v].push_back((Edge){v, u, id});
}
int dep[maxn], anc[maxn][15];
bool vis[maxn];
void dfs_1(int x, int fa = -1) {
vis[x] = true;
anc[x][0] = fa;
for(auto &e : G[x]) {
int v = e.v;
if(!vis[v]) {
dep[v] = dep[x] + 1;
dfs_1(v, x);
}
}
}
int lca(int u, int v) {
if(dep[u] < dep[v]) std::swap(u, v);
for(int j = 14; j >= 0; j --) {
int a = anc[u][j];
if(a != -1 && dep[a] >= dep[v]) u = a;
}
if(u == v) return u;
for(int j = 14; j >= 0; j --) {
int a1 = anc[u][j];
int a2 = anc[v][j];
if(a1 != -1 && a2 != -1 && a1 != a2) {
u = a1, v = a2;
}
}
return anc[u][0];
}
bool used[maxm];
int d1[maxn], d2[maxn];
int o_cnt = 0, o_id;
void dfs_2(int x) {
vis[x] = true;
for(auto &e : G[x]) {
if(used[e.id]) continue;
used[e.id] = true;
int v = e.v;
if(vis[v]) {
int l = lca(x, v);
int dis = dep[x] + dep[v] - 2 * dep[l];
int *d;
if(dis & 1) {
d = d2;
} else {
d = d1;
o_cnt ++;
if(o_cnt == 1) o_id = e.id;
}
d[x] ++; d[v] ++; d[l] -= 2;
} else {
dfs_2(v);
}
}
}
void dfs_3(int x) {
vis[x] = true;
for(auto &e : G[x]) {
int v = e.v;
if(!vis[v]) {
dfs_3(v);
d1[x] += d1[v];
d2[x] += d2[v];
}
}
}
bool allow[maxm], expc[maxm];
void dfs_4(int x) {
vis[x] = true;
for(auto &e : G[x]) {
int v = e.v, id = e.id;
if(!vis[v]) {
if(d1[v] == o_cnt) {
allow[id] = true;
}
if(d2[v] > 0) {
expc[id] = true;
}
dfs_4(v);
}
}
}
std::vector<int> ret;
void solve() {
memset(anc, -1, sizeof(anc));
for(int i = 1; i <= n; i ++) {
if(!vis[i]) dfs_1(i);
}
for(int j = 1; (1 << j) < n; j ++) {
for(int i = 1; i <= n; i ++) {
int a = anc[i][j - 1];
if(a != -1) {
anc[i][j] = anc[a][j - 1];
}
}
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i ++) {
if(!vis[i]) dfs_2(i);
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i ++) {
if(!vis[i]) dfs_3(i);
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i ++) {
if(!vis[i]) dfs_4(i);
}
if(o_cnt == 0) {
for(int i = 1; i <= m; i ++) ret.push_back(i);
} else {
for(int i = 1; i <= m; i ++) {
if(allow[i] && (!expc[i])) {
ret.push_back(i);
} else if(o_cnt == 1 && o_id == i) {
ret.push_back(i);
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) {
int u, v; scanf("%d%d", &u, &v);
ins_edge(u, v, i);
}
solve();
printf("%d\n", ret.size());
bool flag = false;
for(auto i : ret) {
if(flag) putchar(' ');
flag = true;
printf("%d", i);
}
puts("");
return 0;
}
[CF 711D]Directed Roads
这个题啊……其实不难。
首先你注意,他告诉你你可以把边弄反。
其次注意,这个图不一定就有一个环。
然后每个环的取反法有[tex]2^x[/tex]种(假设环中有[tex]x[/tex]条边),但是空集不行,全集也不行,因此每个环爆掉的方案有[tex]2^x-2[/tex]种。然后环之外的边随便弄,乘法原理乱搞即可。
然后你是不是感觉这个题似曾相识?是的似乎这个题就是NOIP D1T2的翻版。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=200001;
const ll MOD=1000000007;
int G[maxn];
int in[maxn];
bool vis[maxn];
int n;
inline bool jianz(){
register int i;
bool ans=false;
for(i=1;i<=n;i++){
if(!vis[i] && !in[i]){
ans=true;
in[G[i]]--;
vis[i]=true;
G[i]=0;
}
}
return ans;
}
ll dfs(int x,int y){
if(x==y && vis[x]){
return 0;
}else{
vis[x]=true;
return dfs(G[x],y)+1;
}
}
ll pow_mod(ll a,ll b){
if(!b){
return 1;
}else{
ll ans=pow_mod(a,b/2);
ans=ans*ans%MOD;
if(1&b){
ans=(ans*(a%MOD))%MOD;
}
return ans;
}
}
inline ll solve(){
register int i;
register ll Huan,temp=0,ans=1;
while(jianz());
for(i=1;i<=n;i++)
if(!vis[i]){
Huan=dfs(i,i);
temp+=Huan;
ans=(ans*(pow_mod(2,Huan)+MOD-2))%MOD;
}
ans=(ans*pow_mod(2,n-temp))%MOD;
return ans;
}
int main(){
register int i;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&G[i]);
in[G[i]]++;
}
printf("%I64d\n",solve());
return 0;
}
[CF 710E]Generate a String
很不错的题。
很容易想到大爆搜:每搜索一个点,预处理只插入的深度,然后限制之。之后再加一些其他的剪枝。不过估计会炸。
然后有同学就想:那个删除操作真特么棘手啊……因为这个就用不了DP了……哎
不过,删除的目的是什么?下一步干啥?再插入?那就有病了。肯定是要复制。所以说我们可以想出如下状态转移方程(i11r的TeX插件好像不能排版多行公式,所以分两部分写吧):
[tex]i[/tex]为偶数时[tex]f[i]=min(f[i-1]+x,f[i/2]+y)[/tex]
[tex]i[/tex]为奇数时[tex]f[i]=min(f[i-1]+x,f[(i+1)/2]+x+y)[/tex]
然后问题就迎刃而解了。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e7+2;
typedef long long ll;
#define REP(i,n) for(i=1;i<=(n);i++)
ll d[maxn];
int main(){
ll n,x,y;
cin>>n>>x>>y;
register ll i;
d[0]=0;
REP(i,n+1)
d[i]=x*i;
REP(i,n){
if(1&i){
d[i]=min(d[i-1]+x,d[(i+1)/2]+x+y);
}else{
d[i]=min(d[i-1]+x,d[i>>1]+y);
}
}
cout<<d[n]<<endl;
return 0;
}
[CF 707C]Pythagorean Triples
很好的数学题啊……
一看就应该想起来构造,对于[tex]n[/tex]为奇数的情况,我们可以假设[tex]n[/tex]为最小数。由此,可以构造出来[tex](n,(n^{2}-1)/2,(n^{2}-1)/2+1)[/tex]一组勾股数(具体证明自己证去)。
[tex]n[/tex]为偶数时呢?化成奇数做?不好,因为这样会出现对于[tex]n^{a} (a>1)[/tex]一类数会无能为力。偶数也可构造:[tex](n,(n/2)^{2}-1,(n/2)^{2}+1)[/tex]。
然后做就行了……
#include <iostream>
using namespace std;
int main(){
long long n,temp;
cin>>n;
if(n<=2){
cout<<-1<<endl;
return 0;
}
if(1&n){
temp=n*n-1;
temp/=2;
cout<<temp<<' '<<(temp+1)<<endl;
}else{
temp=n/2;
cout<<temp*temp+1<<' '<<temp*temp-1<<endl;
}
return 0;
}
[CF 371B]Fox Dividing Cheese
狐狸的三种操作的实质是除二,除三,除五。由此我们可以猜想在最优策略下,两块蛋糕最后的重量应该是[tex]gcd(a,b)[/tex]。然后试除即可。
(大胆猜想,不用证明)
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int gcd(int a,int b){
if(!b)
return a;
else
return gcd(b,a%b);
}
static int P[3]={2,3,5};
inline int min_fj(int source,int direction){
register int i,ans=0;
source/=direction;
if(source==1)
return 0;
for(i=2;i>=0;i--){
while(source%P[i]==0){
source/=P[i];
ans++;
}
}
if(source==1)
return ans;
else
return -1;
}
int main(){
register int direction,t1,t2;
int a,b;
cin>>a>>b;
if(a==b){
cout<<0<<endl;
return 0;
}
direction=gcd(a,b);
t1=min_fj(a,direction);
if(t1==-1){
cout<<-1<<endl;
return 0;
}
t2=min_fj(b,direction);
if(t2==-1){
cout<<-1<<endl;
return 0;
}
cout<<t1+t2<<endl;
return 0;
}