7月22-集训个人赛第三场
2013-07-22 09:38 bootstar 阅读(209) 评论(0) 编辑 收藏 举报A题,简单图论题: Codeforces 295B floyd变种
题意:对于一个带权有向图,每次删除一个点,要求计算输出删除该点前当前图的所有点间的最短路径之和。
解法:对于删除顺序,我们可以反过来做,这样就相当于每次添加一个点到图中,然后询问当前图的所有点间的最短路径之和。对于每次添加操作,就相当于用当前添加的点v,去更新整个图的最短路,也就是dist[s][t] = min(dist[s][t], dist[s][v]+dist[v][t]);
对于统计操作,直接暴力枚举即可。整个复杂度为O(n^3),唯一需要注意的就是,本题数据范围,需要用long long 来处理。
#include <stdio.h> #include <string.h> #define maxn 505 typedef long long LL; bool mark[maxn]; LL dist[maxn][maxn]; LL seq[maxn]; LL ans[maxn]; LL min(LL x, LL y){ return x < y ? x : y; } void update(int s, int n){ for(int v = 1; v <= n; v ++){ for(int u = 1; u <= n; u ++){ dist[v][u] = min(dist[v][u], dist[v][s] + dist[s][u]); } } } LL query(int s, int n){ LL ret = 0; for(int i = n, v = seq[i]; i >= s; v = seq[--i]){ for(int j = n, u = seq[j]; j >= s; u = seq[--j]){ ret += dist[v][u]; } } return ret; } int main(){ for(int n; scanf("%d", &n)!=EOF; ){ for(int i = 1; i <= n; i ++){ for(int j = 1; j <= n; j ++) scanf("%I64d", &dist[i][j]); mark[i] = false; } for(int i = 1;i <= n; i ++) scanf("%d", &seq[i]); for(int i = n; i >= 1; i --){ mark[seq[i]] = 1; update(seq[i], n); ans[i] = query(i, n); } for(int i = 1; i <= n; i ++){ printf("%I64d ", ans[i]); } printf("\n"); } return 0; }
B题,简单数学题: Codeforces 154B
题意:有n个人,m个操作。每个人有一个编号i,每个人有两种行为,行为+ i,表示编号为i的人需要去做实验,此时需要满足当前正在工作的人(设编号为j)有gcd(i, j) = 1;行为- i表示编号为i的人退出实验。对于每个操作需要判断是否成功。
解法:操作中最麻烦的就是怎么样查找谁与谁冲突。考虑i,j如果冲突,也就是说gcd(i, j)!=1,那么我们可以对gcd(i, j)分解为若干个素数的乘积,设为pi,显然存在且只存在一个j,使得pi可以整除gcd(i, j)。(否则设存在k与i冲突且pi整除gcd(k, i),那么k肯定与j冲突。)
处理:首先预处理出10^5内的素数,然后维护一个数组用于表示对应素数的倍数,初始化为0。然后对于+操作,进行素数分解,然后对于每个素数进行查询倍数,如果不是0,就出现了冲突。对于- 操作,同样进行素数分解,并将对应的素数的倍数赋值为0.对于已操作过,这个可以直接利用一个数组进行标记。整个复杂度为O(nlogn + nsqrt(n)).
#include <stdio.h> #include <string.h> #define maxn 100000 bool f[maxn+5]; int pos[maxn]; int p[maxn/10], g[maxn/10]; int total; void process(int n){ memset(f, 0, sizeof(f)); total = 0; for(int i = 2; i <= n; i ++){ if(!f[i]){ pos[i] = total; p[total ++] = i; for(int j = i; j <= n; j += i){ f[j] = 1; } } } memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g)); } int solve(int n){ for(int i = 2; i*i <= n; i ++){ if(n%i==0){ if(g[pos[i]]) return g[pos[i]]; while(n%i==0) n/=i; } } if(n!=1 && g[pos[n]]) return g[pos[n]]; return 0; } int refresh(int n, int x){ for(int i = 2; i*i<=n; i ++){ if(n%i==0){ g[pos[i]] = x; while(n%i==0) n/=i; } } if(n!=1) g[pos[n]] = x; return 0; } int main(){ int n, m, x; char cmd[3]; scanf("%d%d", &n, &m); process(n); for(int i = 0; i < m; i ++){ scanf("%s%d", cmd, &x); if(cmd[0]=='+'){ if(f[x]){ printf("Already on\n"); continue; } int t = solve(x); if(t) printf("Conflict with %d\n", t); else { f[x] = 1; refresh(x, x); printf("Success\n"); } } else{ if(!f[x]) printf("Already off\n"); else f[x] = 0, refresh(x, 0), printf("Success\n"); } } return 0; }
C题,简单数学题 Codefoces 150B
题意:对于一个大小为M的字符集,求长度为n的字符串的个数,要求字符串满足对于所有长度为k的连续子串为回文串。
解法:由于要求长度为k的字符串必须为回文串。那么我们可以先预处理一下哪些位置必须放同样的字符(建图操作)。这样我们就可以将整个位置分成x个集合(直接染色),同一个集合内的位置能放的字符必定是一样的。对于每个集合能放的字符的种类是一样的,所以就是m^x.整个算法的复杂度为O((n-k)*k/2 + n + log m)
1 #include <stdio.h> 2 #include <string.h> 3 #define maxn 2005 4 typedef long long LL; 5 const LL mod = 1000000007; 6 7 int g[maxn][maxn]; 8 int col[maxn], cnt; 9 10 LL quick_power(LL a, LL b){ 11 LL r = 1; 12 for(;b;b>>=1){ 13 if(b&1) r = a * r %mod; 14 a = a * a %mod; 15 } 16 return r; 17 } 18 void dfs(int v, int f, int c, int n){ 19 col[v] = c; 20 for(int i = 1; i <= n; i ++){ 21 if(!g[v][i]|| i == f || col[i]) continue; 22 dfs(i, v, c, n); 23 } 24 } 25 int main(){ 26 int n, m, k; 27 scanf("%d%d%d", &n, &m, &k); 28 if(k > n){ 29 printf("%I64d\n", quick_power(m, n)); 30 return 0; 31 } 32 for(int i = 1; i + k - 1<= n; i ++){ 33 for(int a = i, b = i + k - 1; a < b; a ++ , b --){ 34 g[a][b] = g[b][a] = 1; 35 } 36 } 37 for(int i = 1; i <= n; i ++){ 38 if(!col[i]){ 39 cnt ++; 40 dfs(i, -1, cnt, n); 41 } 42 } 43 printf("%I64d\n", quick_power(m, cnt)); 44 return 0; 45 }
D题,数据结构:线段树 Codefoces 266E
题意:这里就不在描述了,题目很短= =
解法:这个题和杭州邀请赛的C题是同一类型的。主要是对于
的计算。0<=k<=5,可以考虑直接展开(i-l+1)^k:
k = 0: 1
k = 1: i - l + 1
k = 2: i^2 - 2(l-1)i + (l-1)^2
k = 3: i^3 - 3i^2(l-1) + 3i(l-1)^2 - (l-1)^3
k = 4: i^4 - 4i^3(l-1) + 6i^2(l-1)^2 - 4i(l-1)^3 + (l-1)^4
k = 5: i^5 - 5i^4(l-1) + 10i^3(l-1)^2 - 10i^2(l-1)^3 + 5i(l-1)^4 - (l-1)^5
那么由于l-1项只与l有关,我们只需要用线段树维护:i^5ai , i^4ai, i^3ai, i^2ai, iai, ai的区间和。然后剩余的就是线段树的工作了。对于查询操作,可以直接将区间的上述和直接计算到,然后可以用按照二项式展开统一计算。
#include <stdio.h> #include <string.h> #define maxn 100005 #define lson(x) (x<<1) #define rson(x) (x<<1|1) #define md(x, y) ((x+y)>>1) typedef long long LL; #define MOD 1000000007 LL val[6][maxn]; LL ans[6]; int C[6][6]; struct Tree{ LL f[6][maxn*4]; LL v[maxn*4]; void clear(){ memset(f, 0, sizeof(f)); memset(v, 0, sizeof(v)); } void build(int c, int left, int right){ if(left == right){ scanf("%I64d", &v[c]); LL t = 1; for(int i = 0; i <= 5; i ++){ f[i][c] = t * v[c]%MOD; t = t * left %MOD; } return ; } int m = md(left, right); build(lson(c), left, m); build(rson(c), m + 1, right); push_up(c); } void push_down(int c, int left, int right){ if(v[c] == -1) return; int l = lson(c), r = rson(c); v[l] = v[r] = v[c]; int m = md(left, right); for(int i = 0; i <= 5; i ++){ f[i][l] = v[l] * (((val[i][m] - val[i][left-1])%MOD + MOD)%MOD)%MOD; f[i][r] = v[r] * (((val[i][right] - val[i][m])%MOD + MOD)%MOD)%MOD; f[i][c] = 0; } v[c] = -1; } void push_up(int c){ int l = lson(c), r = rson(c); for(int i = 0; i <= 5; i ++){ f[i][c] = (f[i][l] + f[i][r])%MOD; } v[c] = -1; } void update(int c, int l, int r, int left, int right, LL x){ if(l <= left && right <= r){ v[c] = x; for(int i = 0; i <= 5; i ++){ f[i][c] = x*(((val[i][right] - val[i][left-1])%MOD + MOD)%MOD)%MOD; } return ; } push_down(c, left, right); int m = md(left, right); if(r <= m) update(lson(c), l, r, left, m, x); else if(l > m) update(rson(c), l, r, m + 1, right, x); else{ update(lson(c), l, m, left, m, x); update(rson(c), m + 1, r, m + 1, right, x); } push_up(c); } void query(int c, int l, int r, int left, int right, LL ans[]){ if(l <= left && right <= r){ for(int i = 0; i <= 5; i ++){ ans[i] = (f[i][c] + ans[i])%MOD; } return ; } push_down(c, left,right); int m = md(left, right); if(r <= m) query(lson(c), l, r, left, m, ans); else if(l > m) query(rson(c), l, r, m+1, right, ans); else { query(lson(c), l, m, left, m, ans); query(rson(c), m + 1, r, m + 1, right, ans); } push_up(c); } }; Tree tree; void init(int n){ for(int i = 1; i <= n; i ++){ LL t = 1; for(int j = 0; j <= 5; j ++){ val[j][i] = (val[j][i-1] + t)%MOD; t = (t * i)%MOD; } } C[0][0] = 1; C[1][1] = C[1][0] = 1; for(int i = 2; i <= 5; i ++){ C[i][0] = 1; for(int j = 1; j <= i; j ++){ C[i][j] = C[i-1][j] + C[i-1][j-1]; } } } int main(){ int n, m, l, r, x; char cmd[2]; LL t; scanf("%d%d", &n, &m); getchar(); init(n); tree.build(1, 1, n); for(;m --;){ scanf("%s%d%d%d", cmd, &l, &r, &x); if(cmd[0] == '?'){ memset(ans, 0, sizeof(ans)); tree.query(1, l, r, 1, n, ans); LL result = 0; t = 1; for(int i = x, j = 0; i >=0; i --){ if(j&1) result = (result - ((ans[i]*t)%MOD)*C[x][j])%MOD; else result = (result + ((ans[i]*t)%MOD)*C[x][j])%MOD; t = (t * (l-1))%MOD; j ++; } printf("%I64d\n", (result+MOD)%MOD); } else tree.update(1, l, r, 1, n, x); } return 0; }
E题:DP题,一个简单的树形DP, Codefoces 161D
题意:题意比较简单,不在做描述了.
解法:简单树形DP。
答案分成两部分:
1.a, b两个点的距离为k,且,a为b的祖先
2.a, b两个点的距离为k,a,b分别在两颗不同的子树上。
dp[i][k]表示,以i为根的子树上,与i的距离为k的节点个数。
那么显然:dp[i][k] = sum{dp[s][k-1]}其中s是i的孩子节点。
对于第一部分的答案,显然就是dp[i][k]
对于第二部分的答案,是sum{dp[i][a]*dp[s][b]}其中s是当前待处理的孩子节点。
#include <stdio.h> #include <string.h> #include <vector> using namespace std; #define maxn 50005 vector<int> vec[maxn]; int dp[maxn][505]; long long ans; void dfs(int v, int f, int k){ for(int i = 0; i <= k; i ++) dp[v][i] = 0; dp[v][0] = 1; for(int i = 0; i < vec[v].size(); i ++){ int u = vec[v][i]; if(u==f) continue; dfs(u, v, k); for(int j = 0; j < k; j ++){ ans += dp[u][j] * dp[v][k - j - 1]; } for(int j = 1; j < k; j ++){ dp[v][j] += dp[u][j-1]; } } ans += dp[v][k]; } int main(){ for(int n, k; scanf("%d%d", &n, &k)!=EOF; ){ for(int i = 1; i <= n; i ++) vec[i].clear(); for(int i = 1, x, y; i < n; i ++){ scanf("%d%d", &x, &y); vec[x].push_back(y); vec[y].push_back(x); } ans = 0; dfs(1, 0, k); printf("%I64d\n", ans); } return 0; }
F题,比较有意思的字符串题,这个是智商题,表示智商拙计。Codeforces 224D
题意:给定两个串S和T,求是不是对于所有的Si,至少存在一个子串sub, sub是S的子串(不一定连续),Si属于sub,且sub==T.
解法:对于S串的每个位置i,求出以i为终点的S1...Si与T1...Tt的最长公共子序列的长度l[i],
然后求出以i为终点的Ss..Si+1Si与Tt...T1的最长公共子序列的长度r[i]。
然后直接判断向左匹配的最大长度l[i]与向右匹配的最大长度r[i],是不是有l[i]+r[i]-1 >= t。
由于只要求解长度,我们对于每个字符可以记录一个最长匹配长度,这样就可以做到O(n)的预处理匹配长度,然后可以做到O(n)的判断。
1 #include <stdio.h> 2 #include <string.h> 3 #define maxn 200005 4 5 char s[maxn], t[maxn]; 6 int l[maxn], r[maxn], pos[26]; 7 8 int main(){ 9 scanf("%s%s", s + 1, t + 1); 10 int ls = strlen(s + 1), lt = strlen(t + 1); 11 12 memset(pos, -1, sizeof(pos)); 13 for(int i = 1, j = 1; s[i]; i ++){ 14 if(t[j] && s[i]==t[j]){ 15 pos[s[i]-'a'] = j++; 16 } 17 l[i] = pos[s[i] - 'a']; 18 } 19 memset(pos, -1, sizeof(pos)); 20 for(int i = ls, j = 1; i > 0; i --){ 21 if(j <= lt && s[i]==t[lt - j + 1]){ 22 pos[s[i]-'a'] = j ++; 23 } 24 r[i] = pos[s[i]-'a']; 25 } 26 bool f = true; 27 for(int i = 1; s[i]; i ++){ 28 if(l[i] == -1 || r[i] == -1 || l[i] + r[i] < lt + 1){ 29 f = false; 30 break; 31 } 32 } 33 puts(f? "Yes" : "No"); 34 return 0; 35 }