本次比赛题解:戳这里 ——> 题解
写在前面
T1 复读数组
题目传送门:复读数组
题目描述
分析
计算每个位置对答案的贡献(相当于求包含一个点的区间的个数)。为了防止重算漏算,我们只计算区间中第一次出现这个值的位置的贡献,这需要我们预先求出每个数的前一个出现的位置。
对于重复的部分,完全被包含于原始数组的区间会重复k次,而跨了两个区间的长度小于n的区间会重复k-1次。
对于跨区间,且长度大于等于n的区间,答案是固定的,且对于区间起点的区间个数是等差数列,直接等差数列求和得到
我的代码保留了暴力程序
代码
/****************** User:fxyly Language:c++ Problem: Algorithm; ******************/ #include<bits/stdc++.h> using namespace std; const int maxn = 2e5 + 5; const int mod = 1e9 + 7; const int inv2 = 5e8 + 4; long long n,k,cnt; long long a[maxn],tmp[maxn]; long long t[maxn],pre[maxn],pos[maxn],suc[maxn]; long long size[maxn]; bool vis[maxn]; long long ans = 0; template<class T>inline void read(T &x){ x = 0;char ch = getchar();bool flag = 0; while(!isdigit(ch)) flag |= ch == '-',ch = getchar(); while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar(); if(flag) x = -x; } template<class T>void putch(const T x){if(x > 9) putch(x / 10);putchar(x % 10 | 48);} template<class T>void put(const T x){if(x < 0) putchar('-'),putch(-x);else putch(x);} void readdata(){ read(n);read(k); for(int i = 1;i <= n; ++ i) read(a[i]),tmp[i] = a[i]; sort(tmp + 1,tmp + n + 1); cnt = unique(tmp + 1,tmp + n + 1) - (tmp + 1); for(int i = 1;i <= n; ++ i){ a[i] = lower_bound(tmp + 1,tmp + cnt + 1,a[i]) - tmp; a[i + n] = a[i]; } } void work(){//n<=1000 暴力程序,我决定保留 memset(t,0,sizeof(t));memset(size,0,sizeof(size)); //size是桶,t是答案 long long ans1 = 0; for(int i = 1;i <= n; ++ i){//计算一个区间内的情况,会重复k次 for(int j = i;j <= n; ++ j){ if(!size[a[j]]) t[j] = t[j - 1] + 1; else t[j] = t[j - 1]; size[a[j]]++;ans += t[j]; } for(int j = i;j <= n; ++ j) t[j] = 0,size[a[j]] = 0; } if(k == 1) { put(ans);return; } memset(t,0,sizeof(t));memset(size,0,sizeof(size)); for(int i = 1;i <= n; ++ i){//计算跨两个区间的情况,会重复k-1次 for(int j = i;j < n + i; ++ j){ if(!size[a[j]]) t[j] = t[j - 1] + 1; else t[j] = t[j - 1]; size[a[j]]++; if(j >= n + 1) ans1 += t[j]; } for(int j = i;j < n + i; ++ j) t[j] = 0,size[a[j]] = 0; } ans1 = ans1 * (k - 1) % mod; ans = (ans * k % mod + ans1) % mod; //当区间长度大于n时,答案是固定的,直接算,区间个数等差数列 ans1 = cnt * (1 + k * n % mod - n + mod) % mod * (k * n % mod - n + mod) %mod * inv2%mod; ans = (ans + ans1) % mod; put(ans); } void work1(){//zhengjie ,计算每个数的贡献 for(int i = 1;i <= (n<<1); ++ i){pre[i] = pos[a[i]];pos[a[i]] = i;} for(int i = 1;i <= n; ++ i)//只有当这个数是区间里第一次出现的,才算它的贡献 ans = (ans + (i - pre[i]) * (n - i + 1) % mod) % mod; long long ans1 = 0; for(int i = 1;i <= n; ++ i) ans1 = (ans1 + (pre[i] + i - 1) * (i - pre[i]) % mod * inv2 % mod) % mod; for(int i = n+1;i <= (n << 1); ++ i) { if(pre[i] <= n) ans1 = (ans1 + (n * 3 - 2 * i + pre[i] + 1) * (n - pre[i]) % mod * inv2 % mod) % mod; } ans1 = ans1 * (k - 1) % mod; ans = (ans * k % mod + ans1) % mod; //当区间长度大于n时,答案是固定的,直接算,区间个数等差数列 ans1 = cnt * (1 + k * n % mod - n + mod) % mod * (k * n % mod - n + mod) %mod * inv2%mod; ans = (ans + ans1) % mod; put(ans); } int main(){ // freopen("a.in","r",stdin); readdata(); if(n <= 1000) work1(); else work1(); return 0; }
T2 路径计数机
题目传送门:路径计数机
题目描述
分析
链条不相交的路径的条件:对于其中的一条,另一条的lca与之不等且两条路径都不经过另一条路径的lca与fa[lca]的边。
用树形DP维护以点i为LCA的路径数,经过i及fa[i]这条边的路径数(乘法原理)
代码
/****************** User:fxyly Language:c++ Problem: Algorithm: ******************/ #include<bits/stdc++.h> using namespace std; const int maxn = 3005; int p,q,n,size,first[maxn]; int ind[maxn],vis[maxn]; long long fp[maxn],fq[maxn],gp[maxn],gq[maxn]; long long f[maxn][maxn],g[maxn][maxn]; //f[i][j] - i的子树中与i距离为j的路径的个数 ,g[i][j] - 不在i的子树中与i距离为j的路径的个数 //fp[i]/fq[i] - i的子树以i为lca的长为p/q的路径的条数 //gp[i]/gq[i] - 经过i与其父亲的连边的长为p/q的路径的条数 bool judge = 1; struct Edge{ int v,nt; }edge[maxn << 1]; template<class T>inline void read(T &x){ x = 0;char ch = getchar();bool flag = 0; while(!isdigit(ch)) flag |= ch == '-',ch = getchar(); while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar(); if(flag) x = -x; } template<class T>void putch(const T x){if(x > 9) putch(x / 10);putchar(x % 10 | 48);} template<class T>void put(const T x){if(x < 0) putchar('-'),putch(-x);else putch(x);} void eadd(int u,int v){edge[ ++ size].v = v;edge[size].nt = first[u];first[u] = size;} void readdata(){ read(n);read(p);read(q); for(int i = 1;i < n; ++ i){ int u,v;read(u);read(v); eadd(u,v);eadd(v,u);ind[u]++,ind[v]++; if(ind[u] > 2 || ind[v] > 2) judge = 0; } } void work(){//lian int root = 0; long long ans = 0; for(int i = 1;i <= n; ++ i){ int j = i + p + 1; if(j <= n - q) ans = ans + (n - q - j + 1); else break; } for(int i = 1;i <= n; ++ i){ int j = i + q + 1; if(j <= n - p) ans = ans + (n - p - j + 1); else break; } ans = ans * 4; put(ans); } void dfs1(int u,int fa){ f[u][0] = 1; for(int i = first[u];i;i = edge[i].nt){ int v = edge[i].v;if(v == fa) continue; dfs1(v,u); for(int j = 0;j < p; ++ j) fp[u] += f[u][j] * f[v][p - j - 1]; for(int j = 0;j < q; ++ j) fq[u] += f[u][j] * f[v][q - j - 1]; for(int j = 1;j <= max(p,q); ++ j) f[u][j] += f[v][j - 1]; } } void dfs2(int u,int fa){ for(int i = 0;i <= p; ++ i) gp[u] += f[u][i] * g[u][p - i]; for(int i = 0;i <= q; ++ i) gq[u] += f[u][i] * g[u][q - i]; for(int i = first[u];i;i = edge[i].nt){ int v = edge[i].v;if(v == fa) continue; g[v][1] = 1; for(int j = 2;j <= max(p,q); ++ j) g[v][j] += f[u][j - 1] - f[v][j - 2] + g[u][j - 1]; dfs2(v,u); } } void work2(){ dfs1(1,0);dfs2(1,0); long long ans = 0,ansp = 0,ansq = 0; for(int i = 1;i <= n; ++ i) ansp += fp[i],ansq += fq[i]; ans = ansp * ansq; for(int i = 1;i <= n; ++ i) ans -= (fp[i] * gq[i] + fq[i] * gp[i] + fp[i] * fq[i]); put(ans << 2); } int main(){ // freopen("b.in","r",stdin); readdata(); if(judge) work(); else work2(); return 0; }
T3 排列计数机
题目传送门:排列计数机
题目描述
分析
我……现在只会部分分,日后再来填坑
注意子序列不是子串