Codeforces #201 div1
2015-06-27 14:19:45
【传送门】
总结:虚拟参赛的一场,题目都很有质量!比的不是很好,rank540+
赛后补到第三题。
A题:数学
题意:给出 n (n<=100)个互不相同的数的集合,Alice 和 Bob 轮流进行操作,每次操作取出任意两个数,将他们差的绝对值加入集合(注意,这个绝对值一定不能已经在集合中),不能操作的人输。
思路:仔细思考会发现大部分的集合操作到最后都是 1,2,3... maxval,maxval是指原来集合中的最大值。但是有那么几种,比如 3 9 12 操作到最后是 3 6 9 12,其实只要求出原来集合中所有数的gcd,操作到最后集合一定是:gcd,2*gcd,....,maxval,本质其实就是数的拆分的过程,知道这个结论后就可以做咯。
#include <cstdio> #include <ctime> #include <cstring> #include <cstdlib> #include <cmath> #include <vector> #include <map> #include <set> #include <stack> #include <queue> #include <string> #include <iostream> #include <algorithm> using namespace std; #define getmid(l,r) ((l) + ((r) - (l)) / 2) #define MP(a,b) make_pair(a,b) #define PB(a) push_back(a) typedef long long ll; typedef pair<int,int> pii; const double eps = 1e-8; const int INF = (1 << 30) - 1; const int MAXN = 110; int n,A[MAXN]; int main(){ int tmax = 0,G; scanf("%d",&n); for(int i = 1; i <= n; ++i){ scanf("%d",&A[i]); tmax = max(tmax,A[i]); if(i == 1) G = A[1]; else G = __gcd(G,A[i]); } int cnt = tmax / G - n; if(cnt & 1) printf("Alice\n"); else printf("Bob\n"); return 0; }
B题:KMP,DP
题意:给出三个串 s1,s2,s3,求出 s1、s2 的最长公共子序列(与子串不同,序列意味着不一定连续),且 s3 不是这个子序列的子串。注意三个串的长度均小于等于 100。
思路:比较容易想到三维 DP,dp[i][j][k]表示考虑 s1 前 i 个字符,s2 前 j 个字符,且已经匹配了 s3 串前 k 个字符的最长子序列长度。
因为要输出最长子序列,所以要记录一下路径,这些都不是问题,关键在于与 s3 失配之后并不是简单地从头重新匹配,而是应该通过KMP求出的失配数组返回到前一个应该考虑的位置 P[k]。所以 dp 时如果 s1[i] == s2[j] != s3[k] 时,应该用类似 KMP 的过程求出上一个匹配位置 k' 再转移。
#include <cstdio> #include <ctime> #include <cstring> #include <cstdlib> #include <cmath> #include <vector> #include <map> #include <set> #include <stack> #include <queue> #include <string> #include <iostream> #include <algorithm> using namespace std; #define getmid(l,r) ((l) + ((r) - (l)) / 2) #define MP(a,b) make_pair(a,b) #define PB(a) push_back(a) typedef long long ll; typedef pair<int,int> pii; const double eps = 1e-8; const int INF = (1 << 30) - 1; int dp[102][102][102]; int pre[102][102][102]; char s1[102],s2[102],v[102]; vector<char> g; int P[102]; void Get_P(){ memset(P,0,sizeof(P)); int j = 0; int len = strlen(v + 1); for(int i = 2; i <= len; ++i){ while(j > 0 && v[j + 1] != v[i]) j = P[j]; if(v[j + 1] == v[i]) j++; P[i] = j; } } void Print(int a,int b,int c){ if(pre[a][b][c] == -1) return; if(pre[a][b][c] == -10){ g.PB(s1[a]); Print(a - 1,b - 1,c - 1); } else if(pre[a][b][c] >= 0){ g.PB(s1[a]); Print(a - 1,b - 1,pre[a][b][c]); } else if(pre[a][b][c] == -20){ Print(a - 1,b,c); } else if(pre[a][b][c] == -30){ Print(a,b - 1,c); } } int main(){ memset(pre,-1,sizeof(pre)); scanf("%s%s%s",s1 + 1,s2 + 1,v + 1); Get_P(); int l1 = strlen(s1 + 1); int l2 = strlen(s2 + 1); int lv = strlen(v + 1); int ans = 0,ansi,ansj,ansk; for(int i = 0; i <= l1; ++i){ for(int j = 0; j <= l2; ++j){ for(int k = 0; k < lv; ++k){ if(s1[i + 1] == s2[j + 1]){ if(k + 1 < lv && s1[i + 1] == v[k + 1]){ if(dp[i + 1][j + 1][k + 1] < dp[i][j][k] + 1){ dp[i + 1][j + 1][k + 1] = dp[i][j][k] + 1; pre[i + 1][j + 1][k + 1] = -10; } } if(s1[i + 1] != v[k + 1]){ int tk = k; while(tk && s1[i + 1] != v[tk + 1]){ tk = P[tk]; } if(s1[i + 1] == v[tk + 1]) ++tk; if(dp[i + 1][j + 1][tk] < dp[i][j][k] + 1){ dp[i + 1][j + 1][tk] = dp[i][j][k] + 1; pre[i + 1][j + 1][tk] = k; } } } if(dp[i + 1][j][k] < dp[i][j][k]){ dp[i + 1][j][k] = dp[i][j][k]; pre[i + 1][j][k] = -20; } if(dp[i][j + 1][k] < dp[i][j][k]){ dp[i][j + 1][k] = dp[i][j][k]; pre[i][j + 1][k] = -30; } //printf("dp[%d][%d][%d] :%d\n",i,j,k,dp[i][j][k]); if(dp[i][j][k] > ans){ ans = dp[i][j][k]; ansi = i , ansj = j , ansk = k; } } } } //printf("%d %d %d %d\n",ans,ansi,ansj,ansk); if(ans == 0){ printf("0\n"); return 0; } Print(ansi,ansj,ansk); for(int i = g.size() - 1; i >= 0; --i) printf("%c",g[i]); puts(""); return 0; }
C题:贪心
题意:给出 a 和 b (0 ≤ b ≤ a ≤ 10^9, a - b ≤ 10^6),再给出最多 10^5 个数:x1~xn (2 <= xi <= 10^9),我们要把 a 变成 b,每次可以减去 1,或者减去 当前值 % xi,问最少步数。
思路:乍一看没啥思路,虽然有人用暴力乱搞直接过掉了... 但是也有其他的方法(ORZ 杰哥!)
首先我们发现操作的本质就是变成一个 xi 的倍数(在 xi 集合中加入 1,这样减1也符合这个本质了),所以 b 也就必定是某个 xi 的倍数,我们倒着考虑,从 b 加到 a 。首先看 b 是 xi 集合中哪些数的倍数,比如是 xk1 , xk2 , ... , xkm。那么 [b + 1, b + max(xki) - 1] 这些数只用一步操作就能到达 b 了,我们令这个区间为第一区间,我们将刚刚对 b 的操作命为找 b 的最大可达点。然后我们再尝试找第二区间(也就是用两步操作能到达 b 的区间),其实就是找用一步操作能到达第一区间的数,那么需要对第一区间内的所有数找一次最大可达点,然后再求最大值,所以第二区间:[b + max(xki) , max(最大可达点)]。
于是我们就获得了一个将 b 到 a 之间的区域进行划分的做法。第 i + 1 个区间的右端点可以由第 i 个区间内所有数的最大可达点的最大值求得。
显然我们需要一种很快速的方法来求出最大可达点,考虑用筛法,维护一个 w[] 数组,对于所有 xi,考虑其所有的倍数来更新其每个倍数的最大可达点。
w[k * xi] = max(w[k * xi] , k * xi + xi - 1),然后就可以用一遍巧妙的循环来划分区间从而求出最小步数了,具体见程序。
注意点:xi 需要去重~
#include <cstdio> #include <ctime> #include <cstring> #include <cstdlib> #include <cmath> #include <vector> #include <map> #include <set> #include <stack> #include <queue> #include <string> #include <ctime> #include <iostream> #include <algorithm> using namespace std; #define getmid(l,r) ((l) + ((r) - (l)) / 2) #define MP(a,b) make_pair(a,b) #define PB(a) push_back(a) typedef long long ll; typedef pair<int,int> pii; const double eps = 1e-8; const int INF = (1 << 30) - 1; const int MAXN = 100010; int n; int A,B,x[MAXN]; int mp[1000010]; int main(){ scanf("%d",&n); for(int i = 1; i <= n; ++i) scanf("%d",x + i); sort(x + 1,x + n + 1); scanf("%d%d",&A,&B); for(int i = B; i <= A; ++i) mp[i - B] = i + 1; for(int i = 1; i <= n; ++i){ if(i > 1 && x[i] == x[i - 1]) continue; int p = B / x[i] * x[i]; if(p < B) p += x[i]; while(p < A){ mp[p - B] = max(mp[p - B],p + x[i] - 1); p += x[i]; } } int cnt = 0,p = B,nxt_tp = -1,tp = mp[p - B]; while(p < A){ p++; nxt_tp = max(nxt_tp,mp[p - B]); if(p == tp || p == A){ cnt++; tp = nxt_tp; } } printf("%d\n",cnt); return 0; }