2013-5-20 训练赛后总结
题目来源: 2012天津现场赛
A, 背景为麻将的模拟题,按照要求模拟就好。
B,sqrt(N)分解因子,然后暴力算即可
#include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> using namespace std; int sum(int x,int b){ int res = 0; while(x){ res += (x%b)*(x%b); x /= b; } return res; } char mp[50] = "0123456789ABCDEF"; int main(){ int n, m; while( scanf("%d%d", &n,&m) != EOF){ int ans = 0; for(int i = 1; i*i <= n; i++){ if( n%i == 0 ){ if( i*i == n ){ ans += sum(i,m); } else{ ans += sum(i,m); ans += sum(n/i,m); } } } int a[50], n1 = 0; while(ans){ a[n1++] = ans%m; ans /= m; } for(int i = n1-1; i >= 0; i--){ printf("%c", mp[a[i]]); } puts(""); } return 0; }
C,dp( L, x, y ) 表示 前L-2位完全匹配,L-1,L分别为x,y的方案数, 然后转移有三类:
L-1 / L / L+1, 三个都单独转,两个一起, 三个一起, 推一下就可以得到O(1)计算出来
转移方程可以写成 dp( L+1, y1, z ) = Min{ dp(L, x, y ) + cost(三个变换) }
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define MAX(a,b) (a)>(b)?(a):(b) #define MIN(a,b) (a)<(b)?(a):(b) #define count Count const int N = 1010; int s[1005], e[1005]; char str[1005], str2[1005]; int n, dp[1005][11][11]; const int INF = 0x3f3f3f3f; int c3[10][10][10][10][10][10]; int c2[10][10][10][10]; int c1[10][10]; int count(int x1, int x2, int up) { if (up) { if (x2 >= x1) return x2 - x1; else return 10-(x1 - x2); } else { if (x2 <= x1) return x1 - x2; else return 10-(x2 - x1); } } int change1(int x1,int x2){ return min( count(x1,x2,1), count(x1,x2,0) ); } int change2(int x1,int x2,int y1,int y2){ int x_up = count(x1,x2,1), y_up = count(y1,y2,1); int x_down = count(x1,x2,0), y_down = count(y1,y2,0); return min( max(x_up,y_up), max(x_down,y_down) ); } int change3(int x1,int x2,int y1,int y2,int z1,int z2){ int x_up = count(x1,x2,1), y_up = count(y1,y2,1), z_up = count(z1,z2,1); int x_down = count(x1,x2,0), y_down = count(y1,y2,0), z_down = count(z1,z2,0); int res = INF; if( y_up<=x_up && y_up<=z_up ) res = min(res,min(10+y_up,x_up+z_up-y_up)); else res = min( res, max(x_up,max(y_up,z_up)) ); if( y_down<=x_down && y_down<=z_down ) res = min(res,min(10+y_down,x_down+z_down-y_down)); else res = min( res, max(x_down,max(y_down,z_down))); } int comp(int x1,int x2,int y1,int y2,int z1,int z2){ int t = INF; t = min( t, c1[x1][x2] + c1[y1][y2] + c1[z1][z2] ); t = min( t, c1[x1][x2] + c2[y1][y2][z1][z2] ); t = min( t, c2[x1][x2][y1][y2] + c1[z1][z2] ); t = min( t, c3[x1][x2][y1][y2][z1][z2] ); //t = min( t, change1(x1,x2)+change1(y1,y2)+change1(z1,z2) ); //t = min( t, change1(x1,x2)+change2(y1,y2,z1,z2) ); //t = min( t, change2(x1,x2,y1,y2)+change1(z1,z2) ); //t = min( t, change3(x1,x2,y1,y2,z1,z2) ); return t; } void init(){ for(int x1 = 0; x1 < 10; x1++){ for(int x2 = 0; x2 < 10; x2++){ c1[x1][x2] = change1(x1,x2); for(int y1 = 0; y1 < 10; y1++){ for(int y2 = 0; y2 < 10; y2++){ c2[x1][x2][y1][y2] = change2(x1,x2,y1,y2); for(int z1 = 0; z1 < 10; z1++){ for(int z2 = 0; z2 < 10; z2++){ c3[x1][x2][y1][y2][z1][z2] = change3(x1,x2,y1,y2,z1,z2); } } } } } } } void solve() { memset(dp,0x3f,sizeof(dp)); for(int y = 0; y < 10; y++){ dp[1][0][y] = change1(s[1],y); } for(int L = 1; L < n; L++) { for(int x = 0; x < 10; x++) for(int y = 0; y < 10; y++) { if( dp[L][x][y] == INF ) continue; for(int y1 = 0; y1 < 10; y1++) for(int z = 0; z < 10; z++){ int t = comp(x,e[L-1],y,y1,s[L+1],z ); dp[L+1][y1][z] = min( dp[L+1][y1][z], dp[L][x][y] + t ); } } } printf("%d\n", dp[n][e[n-1]][e[n]] ); } int main() { // printf("(5,2,1) = %d, (2,2,1) = %d\n", change3(5,9,2,9,1,5), change3(2,9,2,9,1,5) ); init(); while (scanf("%s %s", str + 1, str2 + 1) != EOF) { n = strlen(str+1); s[0] = e[0] = 0; for (int i = 1; i <= n; ++i) { s[i] = str[i] - '0'; e[i] = str2[i] - '0'; } solve(); } return 0; }
D, N个点围成一圈, 每个点有个权值,可以修改: i变为 -Ai, i-1与i+1位置+Ai, 不会捉。。。
E. 第i个城市建加油站花费为 2^i, 比 (1,i-1)全部建加油站的和还大,可见,若i城市不建加油站而在 (1,i-1)这些位置多建一些这样更优。 那么我们可以最初令所有城市都建,然后从N到1开始尝试删除加油站,看是否合法。
然后就是删除一个加油站判定问题, 因为未规定一个点只能走一次,要求长度最大为K, 那么当一个点从一个有加油站的城市出发,要么到有加油站的城市,或者到没有加油站的城市, 此时若全部点需要走到,并且能够回到1, 则必定在那些没有加油站的城市能够返回,那么此时从加油站的城市到非加油站的城市 路径长度必须要少于或等于 K/2,这样才能保证返回,另外 从有加油站的城市 到 有加油站的城市 则只要距离小于等于K即可。必定可以返回。 另外,我们假定这样一个结论: 所有非加油站城市 都能通过至少一个 加油站城市 到达。 因为若 从一个 加油站城市 经过 多个非加油站城市 再到 加油站城市, 其路径总长度必定小于等于K, 那么意味着 这两个加油站城市 直接距离必定小于等于K,意味着我们不需要通过 非加油站 城市到达 加油站城市。 所以我们能够通过 BFS,若是加油站城市则 入队,然后判定是否能够走到所有顶点至少一次。
#include<cstdlib> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<cmath> using namespace std; const int N = 150; const int inf = 0x3f3f3f3f; struct node{ int x, y; }city[N]; int mp[N][N]; bool vis[N], sta[N]; int n, K; int dist(int i,int j){ return ceil( sqrt( (city[i].x-city[j].x)*(city[i].x-city[j].x)+(city[i].y-city[j].y)*(city[i].y-city[j].y) ) ); } bool legal(){ queue<int>Q; while( !Q.empty() ) Q.pop(); memset(vis,0,sizeof(vis)); Q.push(0); vis[0] = true; while( !Q.empty() ){ int cur = Q.front(); Q.pop(); for(int nxt = 0; nxt < n; nxt++){ if( cur == nxt ) continue; if( !vis[nxt] ){ if( sta[nxt] ){ if( mp[cur][nxt] <= K ) vis[nxt] = true, Q.push(nxt); } else{ if( mp[cur][nxt] <= K/2 ) vis[nxt] = true; } } } } for(int i = 0; i < n; i++) if( !vis[i] ) return false; return true; } int main(){ while( scanf("%d%d",&n,&K) != EOF){ for(int i = 0; i < n; i++){ scanf("%d%d", &city[i].x, &city[i].y ); } for(int i = 0; i < n; i++) for(int j = i; j < n; j++){ if( i == j ) mp[i][j] = inf; else mp[i][j]=mp[j][i]=dist(i,j); } for(int i = 0; i < n; i++) sta[i] = true; if( legal() == false ){ puts("-1"); continue; } else{ for(int i = n-1; i > 0; i-- ){ sta[i] = false; if( !legal() ) sta[i] = true; } } int k; for(k = n-1; k > 0; k--) if( sta[k] ) break; for( ; k >= 0; k-- ) printf("%d",sta[k]); puts(""); } return 0; }
F. 所有字串问题,听说后缀数组能做,用后缀自动机写会方便。果断去学习下。。。。
G. swap, 依据定义神码是 cup, 虽然矩阵很小,但是还是不知道如何计算。
H. 对于AB顺序,求出X获得 A,B,A+B分的期望, 与 BA顺序,获得A,B,A+B的期望,比较下即可。
#include<cstdio> #include<cstring> #include<cstdlib> const double esp = 1e-8; double sign(double x){ return x<-esp?-1:(x>esp); } int main(){ int T; scanf("%d", &T); while( T-- ){ int A, B; double P, Q; scanf("%d%d%lf%lf", &A,&B,&P,&Q); double P1 = 1-P, Q1 = 1-Q; double tiger = A*( Q1+Q*P*P1 )+B*(Q*P*P1)+(A+B)*(Q*P*P); double wolf = A*(Q1*P1*P) + B*(Q+Q1*P*P1) + (A+B)*(Q1*P*P); if( sign(tiger-wolf) >= 0 ){ printf("tiger %.4lf\n", tiger ); } else printf("wolf %.4lf\n", wolf ); } return 0; }
I. 题目没看,看到别人题解似乎是一道数学题。。。
J. 给定的是多个连通块,然后连通块内部顶点有边,边上有权值, 然后不同连通块间只能坐地铁,问坐地铁不超过K次游览各个连通快最大权值。。。暂时无思路。。。
K. 使用数据结构 Splay Tree 来维护, 题目主要要解决 插入-x位置问题, 因为要满足 队列进出序列,意味着 +x之前的正整数对应的出队操作完成后-x才能出去,之间还可以有一些 数+y进队,但是-x一定要在这些+y出队前出去,所以可以得出 -x的位置,假定 +x左边的正整数的数量为 k, 则-x的位置应该为 左数第k+1个负数位置前一位, 若序列无k+1个负数,则在最后一位。 解决这个问题就好做了。 因为要序列中未出现的最小正整数,我们用个堆放进1-n。然后每次insert就删掉一个最小,若remove就将x加入进去。logN即可。
insert i ,操作, 通过将 i-1旋转到根,再将i旋转到根下,然后插入的节点就是i的左儿子了。这很好做,-x问题就是上面说到的,先找到位置,找到了就与插入+x是一样的了。查找第K+1的负数,与整数的问题,我们可以通过添加一个节点信息来保存子树 负数个数,正数个数来解决。 多个标记即可。还有就是为了方便下面的remove 与 query操作,我们将 键值为 x的 +x,-x在 splay中的指针记录起来,这样就方便 对其进行删除与查询了。我的程序是用 loc[x][2] 分别记录 x, -x 对应的节点指针 p。
remove x, 因为前面记录了loc[x][2],其+x,-x在splay中的节点指针, 则我们对于+x删除, 将其左边第一个与右边第一个分别移动到根与根下,然后删除左儿子即可。注意要往上push_up;
query x, 其实和remove差不多, 如果不太理解可以看看关于 splay维护 数列区间的论文。
最后要注意的是,求和 (1+10^5)*10^5 极端情况可能溢出。。要用64 int 。
#include<cstdio> #include<cstring> #include<cstdlib> #include<queue> #include<string> #include<algorithm> using namespace std; #define keytree ch[ ch[rt][1] ][0] #define ft ch[rt][1] const int N = (int)4e5+100; typedef long long LL; struct Splay{ int ch[N][2],pre[N],sz[N]; int num[N][2], key[N]; LL sum[N]; int rt, top; void rotate(int x,int d){ int y = pre[x]; ch[y][d^1] = ch[x][d]; pre[ ch[x][d] ] = y; if( pre[y] ) ch[pre[y]][ ch[pre[y]][1] == y ] = x; pre[x] = pre[y]; ch[x][d] = y; pre[y] = x; push_up(y); } void splay(int x,int goal){ while( pre[x] != goal ){ int y = pre[x]; if( pre[y] == goal ) rotate( x, ch[y][0]==x ); else{ int z = pre[y]; int d1 = ch[z][0]==y, d2 = ch[y][0]==x; if( d1 == d2 ) rotate(y,d1), rotate(x,d2); else rotate(x,d2), rotate(x,d1); } } if( goal == 0 ) rt = x; push_up(x); } void rotate_to(int k,int goal){ //左边有k个,不包含自身的情况下 int x = rt; while( sz[ ch[x][0] ] != k ){ if( sz[ ch[x][0] ] >= k ) x = ch[x][0]; else k -= (sz[ch[x][0]]+1), x = ch[x][1]; } //printf("x = %d\n", x ); splay( x, goal ); push_up(x); } void NewNode(int &x, int c, int f){ x = ++top; ch[x][0] = ch[x][1] = 0; pre[x] = f; sz[x] = 1; num[x][0] = (c>0); num[x][1] = (c<0); key[x] = sum[x] = c; } void init(){ // init the null point ch[0][0]=ch[0][1]=pre[0]=sz[0]=0; num[0][0]=num[0][1]=key[0]=sum[0]=0; top = rt = 0; // Two Virtual Point NewNode( rt, 0, 0 ); NewNode( ch[rt][1], 0, rt ); push_up(rt); // print(rt); } int Kth(){ int k = num[ ch[rt][0] ][0] + 1; //左数第n+1个负数位置 if( num[rt][1] < k ) return sz[rt]-1; else{ int x = rt, s = 0; while(1){ int d = num[ ch[x][0] ][1]; if( d >= k ) x = ch[x][0]; else if( d+(key[x]<0) == k ) return (s+sz[ ch[x][0] ]); else{ s += 1+sz[ ch[x][0] ]; k -= ( d + ( key[x] < 0 ) ); x = ch[x][1]; } } } } void push_up(int x){ if(x){ sum[x] = key[x] + sum[ ch[x][0] ] + sum[ ch[x][1] ]; sz[x] = 1 + sz[ ch[x][0] ] + sz[ ch[x][1] ]; num[x][0] = (key[x]>0) + num[ ch[x][0] ][0] + num[ ch[x][1] ][0]; num[x][1] = (key[x]<0) + num[ ch[x][0] ][1] + num[ ch[x][1] ][1]; } } int insert(int val,int pos){ //printf("Insert: pos = %d\n", pos ); rotate_to( pos-1, 0 ); rotate_to( pos, rt ); NewNode( keytree, val, ft ); push_up( ft ), push_up(rt); //printf("keytree = %d\n", keytree ); splay( keytree, 0 ); return rt; } void remove(int x){ splay(x, 0); int lch = ch[x][0], rch = ch[x][1]; while( ch[lch][1] ) lch = ch[lch][1]; while( ch[rch][0] ) rch = ch[rch][0]; splay( lch, 0 ); splay( rch, rt ); ch[ft][0] = 0; push_up(ft); push_up(rt); } LL query(int x, int y){ splay(x,0); splay(y,rt); return sum[keytree]; } void print(int x){ if( ch[x][0] ) print( ch[x][0] ); printf("节点指针%d, 键值:%d, 父节点:%d, 左儿子:%d, 右儿子:%d,sum = %lld, sz = %d, num[0] = %d, num[1] = %d\n", x, key[x], pre[x], ch[x][0], ch[x][1], sum[x], sz[x], num[x][0], num[x][1] ); if( ch[x][1] ) print( ch[x][1] ); } }spt; int loc[N][2]; int main(){ freopen("1.in","r",stdin); int n, Case = 1; while( scanf("%d", &n) != EOF){ printf("Case #%d:\n", Case++ ); // init spt.init(); priority_queue<int,vector<int>,greater<int> > Q; for(int i = 1; i <= n; i++) Q.push(i); // operate char op[10]; int x, pos; for(int i = 0; i < n; i++){ scanf("%s", op); if( op[0] == 'i' ){ //insert pos scanf("%d", &pos); x = Q.top(); Q.pop(); loc[x][0] = spt.insert( x, pos+1 ); // must splay(x,0) ,the rt is x // spt.print( spt.rt ); puts("*******"); pos = spt.Kth(); loc[x][1] = spt.insert( -x, pos ); // spt.print( spt.rt ); } else if( op[0] == 'r' ){ //remove x scanf("%d", &x); spt.remove( loc[x][1] ); spt.remove( loc[x][0] ); Q.push(x); } else{ // query x scanf("%d", &x); LL res = spt.query( loc[x][0], loc[x][1] ); printf("%lld\n", res ); } } } return 0; }