2013-5-12 训练赛后总结
感谢 谢大,duoxida,zhsl ,教会了我几个训练赛当中没有想出来的题。
A Force Brute
题意: 没发现其水题本质啊.. 给定 N个单词, 然后问最大循环次数.
解法: KMP next数组的运用, 更详细的可以看这一篇总结 http://www.cnblogs.com/yefeng1627/archive/2013/04/28/3050027.html
这里就简要说下, 因为求next的过程是一个一个构造循环节的, (L+1)-next[L+1] 即为当前字符串的最小循环节长度, 而 L%( (L+1)-next[L+1] ) 表示目前 循环节构造了多少个,若为0则意味着构造满了一个循环. 而 L / ( (L+1) - next[L+1] ) 表是目前已经构造了的循环节数量. 所以最终结果为
令 d = (L+1) - next[L+1] , 则 ans = L/d + (L%d != 0)
若 next[L+1] = 1, 则代表整个串是 最小循环节. 其实发现可以不特殊处理的.
#include<cstdio> #include<cstring> #include<cstdlib> #include<map> #include<string> #include<algorithm> using namespace std; const int N = (int)1e5+10; char str[N*25], s[30]; int nxt[N], a[N], top, n; map<string,int> mp; int main(){ int T; scanf("%d", &T); getchar(); while( T-- ){ gets(str); top = n = 0; mp.clear(); char *p = strtok( str, " " ); while( p ){ if( mp.count(p) == 0 ) mp[p] = ++top; a[n++] = mp[p]; p = strtok( NULL, " " ); } // for(int i = 0; i < n; i++) // printf("%d ", a[i] ); puts(""); int i = 0, j = 1; nxt[1] = 0; while( j <= n ){ if( i == 0 || a[i-1] == a[j-1] ) nxt[++j] = ++i; else i = nxt[i]; } // for(int i = 1; i <= n+1; i++) // printf("%d ", nxt[i] ); puts(""); if( nxt[n+1] != 1 ){ int d = (n+1)-nxt[n+1]; printf("%d\n", n/d + (n%d!=0) ); } else puts("1"); } return 0; }
B God Lin’s Harem
题意: N个点,每个点有个权值, 操作主要是,修改单点,删除单点,查询单点,查询最值下标,相同则输出最近的。
解法: 线段树即可, 初始便建立N个顶点,因为最多N次操作。 然后对于N操作,每次给其一个优先级,这样在查询时,
出现权值相同,则取优先级大的。 对于更新和修改操作。单点更新直接到底就好。查询操作O(1).
顺便说下,更新操作是不影响优先级的。别像我一样,在这里WA好久。
#include<cstdio> #include<cstring> #include<cstdlib> #include<string> #include<algorithm> #include<queue> #include<map> using namespace std; typedef long long LL; #define lch rt<<1,l,m #define rch rt<<1|1,m+1,r const int N = (int)1e5+100; LL tree[N<<2]; int idx[N<<2], cnt[N]; int n, top; void push_up(int rt){ int lc = rt<<1, rc = rt<<1|1; if( (tree[lc]>tree[rc]) || ((tree[lc]==tree[rc])&&(cnt[idx[lc]]>cnt[idx[rc]]) ) ) tree[rt] = tree[lc], idx[rt] = idx[lc]; else tree[rt] = tree[rc], idx[rt] = idx[rc]; } void build(int rt,int l,int r){ tree[rt] = -1; idx[rt] = l; if( l == r ){ return; } int m = (l+r)>>1; build( lch ), build( rch ); push_up( rt ); } void update(int rt,int l,int r,int a,LL b, bool flag){ if( l == r ){ if(flag) tree[rt] = b; else tree[rt] += b; return; } int m = (l+r)>>1; if(a <= m) update( lch, a, b,flag ); else update( rch, a, b,flag ); push_up(rt); } int main(){ freopen("1.in","r",stdin); while( scanf("%d", &n) != EOF ){ if( n == 0 ) break; char op[2]; int x, y, k = 1; bool flag = true; top = 0; memset( cnt, 0, sizeof(cnt)); build( 1, 1, n ); //print(); for(int i = 0; i < n; i++){ scanf("%s", op); switch( op[0] ){ case 'N': scanf("%d",&x); cnt[k] = ++top; update(1,1,n,k++,x,true); break; case 'I': scanf("%d%d",&x,&y); if( cnt[x] == -1 ) continue; //cnt[x] = ++top; update(1,1,n,x,y,false); break; case 'D': scanf("%d%d",&x,&y); if( cnt[x] == -1 ) continue; //cnt[x] = 0; // cnt[x] = ++top; update(1,1,n,x,-y,false); break; case 'E': scanf("%d", &x); cnt[x] = -1; update(1,1,n,x,-1,true); break; case 'S': if( flag ) flag = false; else printf(" "); printf("%d", idx[1] ); } } puts(""); } return 0; }
C Harry Potter
题意: 一个人从0点出发,走到X,每次只能走X+1, 或者X*2. 问最小时间。X <= 10^18.
解法: X小可以用BFS搜索, 但是这里其实可以不用,基于贪心的原则,走最大理论上是最优,但若从前面走则不一定。
那么我们从X走到0,每次走 X/2 ,若X是奇数则 X-1, 这样即为最终结果。
#include<cstdio> typedef long long LL; int main(){ int T; scanf("%d", &T); while( T-- ){ LL x, t = 0; scanf("%lld", &x); while( x ){ if( x&1 ) x--; else x /= 2; t++; } printf("%lld\n", t ); } return 0; }
顺便贴个BFS版本,不过这里A不了的
#include<cstdio> #include<map> #include<queue> #include<algorithm> using namespace std; typedef long long LL; const LL inf = (LL)1e18+10; struct node{ LL x, t; }pre,nxt; queue<node> Q; map<LL,int> mp; LL solve(LL X){ while( !Q.empty() ) Q.pop(); mp.clear(); pre.x = 0, pre.t = 0; mp[ pre.x ] = 1; Q.push( pre ); while( !Q.empty() ){ pre = Q.front(); Q.pop(); if( pre.x == X ) return pre.t; if( pre.x+1 < inf ){ if( mp.count(pre.x+1) == 0 ){ if(pre.x+1==X) return pre.t+1; nxt.x = pre.x+1; nxt.t = pre.t+1; Q.push( nxt ); mp[ nxt.x ] = 1; } } if( pre.x*2 < inf ){ if( mp.count(pre.x*2) == 0 ){ if( pre.x*2 == X ) return pre.t+1; nxt.x = pre.x*2; nxt.t = pre.t+1; Q.push( nxt ); mp[ nxt.x ] = 1; } } } } int main(){ int T; scanf("%d", &T); while( T-- ) { LL K; scanf("%lld", &K); printf("%lld\n", solve(K) ); } return 0; }
D (LCS)^2
题意: 题目很直白啊,两个长度为10^6的串,求最长公共子序列,输出。
解法: 表示不会, O(NlogN)的算法
E N皇后问题
题意: 没看明白
F 查询成绩
题意: 给两个串, s,t , 其中t中部分为字符'*', 问t是否为S的片段。strlen( s, t ) <= 110
解法: 递归匹配,即可。 若 Tj = '*', 则Tj+1 与 Si开始匹配, 若Tj != '*' ,则比较 Tj ,Si. 注意多个*在一起时,只算一个*
#include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; char s[150], t[150]; int la, lb; int find( int x, int y ){ if( (y<lb)&&(x>=la) ) return 0; if( y == lb ) return 1; if( t[y] == '*' ){ while( t[y+1] == '*' ) y++; int tmp; for(int i = x; i < la; i++){ if( s[i] == t[y+1] ){ tmp = find( i+1, y+2 ); if(tmp) return 1; } } return 0; } else{ if( s[x] != t[y] ) return 0; else return find( x+1, y+1 ); } } int main(){ while( scanf("%s %s", s, t+1 ) != EOF){ t[0] = '*'; la = strlen(s), lb = strlen(t); puts( find(0,0) ? "yes" : "no" ); } return 0; }
G 卡片重组
题意: N张带有权值的卡片,顺序摆放,先要求分成多份,并且每份单调,不能交换位置,单分中权值为sum(i,j)*num(i,j) 。
解法: 动态规划
dp( i ) 表示前 i 张卡片最大权值,转移方程为
dp( i ) = Max{ dp(j ) + Sum(j+1,i)* Num( j+1,i) } 其中 j < i, 且 ( j+1, i ) 区间单调
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; typedef long long LL; const int N = 1010; int dp[N], val[N], sum[N]; int n; int main(){ while( scanf("%d", &n) != EOF){ for(int i = 1; i <= n; i++) scanf("%d", &val[i] ); sum[0] = 0; for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+val[i]; memset( dp, 0, sizeof(dp)); for(int i = 1; i <= n; i++){ dp[i] = dp[i-1] + val[i]; int j = i-2; while( (j>=0) && (val[j+1]>val[j+2]) ) dp[i] = max( dp[i], dp[j] + (i-j)*(sum[i]-sum[j]) ), j--; j = i-2; while( (j>=0) && (val[j+1]<val[j+2]) ) dp[i] = max( dp[i], dp[j] + (i-j)*(sum[i]-sum[j]) ), j--; } printf("%d\n", dp[n] ); } return 0; }
H 括号匹配
题意: 给一括号串 ( ?? ) , 其中 ? 可为 ( 或者 ), 问合法方案数
解法: 动态规划
因为括号匹配则,将 左括号 "(" 看作1, 右括号")"看作-1,则合法的括号串其所有前缀和都 >= 0.
又因为串总长为 1000, 则其前缀和最大为 500.
状态方程 dp ( i, j ) 表示 前i个括号,最后一个前缀和为 j的 方案数 (之前的都满足>=0)
转移方程为:
Si = '(' , 则前缀和+1, dp( i, j+1 ) += dp( i-1, j ) , 其中 j+1 <= 500
Si = ')', 则前缀和-1, dp( i, j-1 ) += dp( i-1,j ), 其中 j-1 >= 0
Si = '?', 则前缀和可能+1,也可-1, 同样要满足上面 j+1 <= 500, j-1 >= 0
dp( i, j+1 ) += dp( i-1, j )
dp( i, j-1 ) += dp( i-1, j )
#include<cstdlib> #include<cstdio> #include<cstring> typedef long long LL; const int Mod = (int)1e9+7; const int N = 1010; const int M = 500; LL dp[N][1200]; char s[N]; int main(){ while( scanf("%s", s+1) != EOF){ int n = strlen(s+1); memset(dp,0,sizeof(dp)); dp[0][0+M] = 1; for(int i = 1; i <= n; i++){ for(int j = 0; j <= 500; j++){ if( (s[i] == '(')&&(j+1<=500) ) dp[i][ (j+1)+M ] = (dp[i][(j+1)+M]+dp[i-1][j+M])%Mod; else if( (s[i] == ')') && (j-1>=0) ) dp[i][ (j-1)+M ] = (dp[i][(j-1)+M]+dp[i-1][j+M])%Mod; else if( s[i] == '?') { if( j+1 <= 500 ) dp[i][ (j+1)+M ] = (dp[i][(j+1)+M]+dp[i-1][j+M])%Mod; if( j-1 >= 0 ) dp[i][ (j-1)+M ] = (dp[i][(j-1)+M]+dp[i-1][j+M])%Mod; } } } printf("%lld\n", dp[n][M] ); } return 0; }
I 排列的逆序数
题意: {1,2...n}的所有排列中逆序数为k的排列个数。
解法: 动态规划
状态方程 dp( i, j ) 表示前 i个数,逆序数为 j 的排列数, 因为最大逆序数为 n*(n-1)/2, 当n = 100, 则lim 接近5000
转移方程 dp( i, j+k ) += dp( i-1, j ) , 其中 k表示第i位贡献的逆序数, 取值范围为 [0, n-i], 并且 前i个数的最大逆序数量为 n*i - i*(i+1)/2,
所以 j <= n*i - i*(i+1)/2.
#include<cstdio> #include<cstring> #include<cstdlib> const int mod = (int)1e9+7; typedef long long LL; LL dp[110][5010]; int n, K; int main(){ while( scanf("%d%d", &n,&K) != EOF){ int lim = n*(n-1)/2; memset( dp, 0, sizeof(dp)); dp[0][0] = 1; for(int i = 1; i <= n; i++){ int cnt = i*n-(1+i)*i/2; for(int j = 0; j <= cnt; j++){ if( dp[i-1][j] == 0 ) continue; for(int k = 0; k <= n-i; k++) if( j+k <= lim ) dp[i][j+k] = (dp[i][j+k]+dp[i-1][j])%mod; } } printf("%lld\n", dp[n][K] ); } return 0; }
J 物品选择
题意: N件物品,都有个价值,部分是主件,部分是附件,附件需要在主件被选情况下才可选择。
解法: 背包
首先分组, 假定有 m个主件, 每个主件有 ni个附件。
令 g( i, j ) 表示 从第i个主件中,取 j个物品的最大价值 , 其中 j <= ni, g(i,0)看作只去主件本身
则令 dp( i , j ) 表示前 i个主件 ,取J个物品的最大价值, 转移方程为:
dp( i , j ) = Max{ dp(i-1, j), dp( i-1, j-(k+1) ) + g(i, k) } //其中 j - (k+1) >= 0, k <= mi
然后 g( i, j ) 求法也差不多。
#include<cstdio> #include<cstdlib> #include<vector> #include<algorithm> #include<cstring> using namespace std; const int N = 1010; vector<int> Q[1010]; int g[N][N], d[N][N], dp[N][N], a[N][N]; int n, K; int val[N], v[N], L[N]; bool flag[N]; int sum[N]; int main(){ while( scanf("%d%d", &n,&K) != EOF){ memset(flag,0,sizeof(flag)); for(int i = 0; i <= n; i++) Q[i].clear(); for(int i = 1; i <= n; i++){ int p; scanf("%d%d",&val[i],&p); if( p != i ) Q[p].push_back(val[i]); flag[p] = true; } memset(g,0,sizeof(g)); for(int x = 1; x <= n; x++){ // 第x件物品,其为主件 int len = (int)Q[x].size(); if( flag[x] ){ // 0,1 pack memset(d,0,sizeof(d)); d[0][0] = val[x]; for(int i = 1; i <= len; i++){ for(int j = 0; j <= i; j++){ if( j == 0 ) d[i][j] = d[i-1][j]; else if( j == i ) d[i][j] = d[i-1][j-1] + Q[x][i-1]; else d[i][j] = max( d[i-1][j], d[i-1][j-1]+ Q[x][i-1] ); } } for(int i = 0; i <= len; i++) g[x][i] = d[len][i]; } } int cnt = 0; for(int i = 1; i <= n; i++){ if( flag[i] ){ L[++cnt] = Q[i].size(); for(int j = 0; j <= L[cnt]; j++) a[cnt][j] = g[i][j]; } } memset( dp,0, sizeof(dp)); for(int i = 1; i <= cnt; i++){ for(int j = 0; j <= K; j++){ dp[i][j] = dp[i-1][j]; if( j > 0 ){ for(int k = 0; (k<=L[i]) && (k<j); k++){ dp[i][j] = max( dp[i][j], dp[i-1][j-(k+1)] + a[i][k] ); } } } } printf("%d\n", dp[cnt][K] ); } return 0; }