TopCoder 649 div1 & div2

最近一场TC,做得是在是烂,不过最后challenge阶段用一个随机数据cha了一个明显错误的代码,最后免于暴跌rating,还涨了一点。TC题目质量还是很高的,非常锻炼思维,拓展做题的视野,老老实实补题吧。

div2

250pts

题意:判断s去掉一个字符后能否和t一样。

代码:

 1 class DecipherabilityEasy {
 2     public:
 3     string check(string s, string t) {
 4         int n = s.size();
 5         int m = t.size();
 6         if(n-1 != m) return "Impossible";
 7         for(int i = 0; i < n; i++){
 8                 string tmp = "";
 9             for(int j = 0; j < i; j++)
10             tmp += s[j];
11             for(int j = i+1; j < n; j++)
12                 tmp += s[j];
13             if(tmp == t) return "Possible";
14         }
15         return "Impossible";
16     }
17 };
View Code

500pts

题意:给定一个长度为n的序列,1 <= n <= 100,可以有两种操作,每次可以将序列从任意位置分成两部分,或者让序列长度减少1,每个操作占1min,每一轮中可以对每个序列进行一次以上操作,问是所有序列消失最短时间,其中split操作的次数不超过k。

分析:dp[n][k]表示对长度为n的序列,最多进行k次split操作时,最短时间。

可以得到以下转移:

dp[n][k] = min(dp[n][k], max(dp[i][j], dp[n-i][k-1-j])+1);

dp[n][k] = min(dp[n][k], n).

代码:

 1 int dp[110][110];
 2 class CartInSupermarketEasy {
 3 public:
 4 
 5     int calc(int N, int K) {
 6         memset(dp, -1, sizeof dp);
 7         for(int i = 0; i <= N; i++)
 8             dp[i][0] = i;
 9         for(int i = 0; i <= K; i++)
10             dp[0][i] = 0;
11 //            cout << dp[0][0] << endl;
12 //            cout << (~0) << endl;
13         return dfs(N, K);
14     }
15     int dfs(int x, int y) {
16 //        if(x == 0) return 0;
17 //        if(y == 0) return x;
18         int &res = dp[x][y];
19 //        cout << dp[x][y] << endl;
20         //        cout << res << endl;
21 //        cout << x << y << endl;
22 //        cout << res << endl;
23         if(~res) return res;
24         res = x;
25         res = min(res, dfs(x-1, y) + 1);
26         y--;
27         for(int i = 1; i < x; i++)
28             for(int j = 0; j <= y; j++) {
29                 res = min(res, max(dfs(i,j), dfs(x-i, y-j))+1);
30             }
31 //        if(res == 0) cout << x << y << endl;
32         return res;
33     }
34 };
View Code

解题关键:发现split操作后得到的子序列问题是原问题的子问题。

1000pts

题意:数组A[i],长度n <= 50,求C,令B[i] = A[i]^C,使得B[i]中满足i < j,B[i] < B[j]的数对最多,输出这样的最大值。

分析:考虑两个数A[i],A[j]异或一个数C的大小关系,考虑两数的二进制位,设从第k位开始,A[i],A[j]二进制位不同,也就是k+1, k+2及高位都完全相同,设k位(x, 1-x),那么异或后数B[i],B[j]大小主要与(x,1-x)及C的第k位(记做y)有如下关系:

x = 0,y = 1,B[i] > B[j];    x = 0,y = 0,B[i] < B[j];

x = 1,y = 1,B[i] < B[j];    x = 1,y = 0,B[i]  > B[j].

此题n不大,枚举第k位,确定第k位为0,1时,能够根据第k位为0或1确定的满足条件i < j,且B[i] < B[j]的对数,取两者中最大值。

代码:

 1 class XorSequenceEasy {
 2 public:
 3     int getmax(vector <int> A, int N) {
 4         int ans = 0, B = 0;
 5         int n = sz(A);
 6         N = __builtin_popcount(N-1);
 7 //        cout << N << endl;
 8         for(int i = 0; i < N; i++) {
 9             int tmp[2];
10             tmp[0] = tmp[1] = 0;
11             for(int k = 0; k < n; k++)
12                 for(int l = k+1; l < n; l++) {
13                     if(((A[k]^A[l])>=(1<<i)) &&
14                             ((A[k]^A[l])<(1<<(i+1))))
15                         tmp[(A[k]>>i)&1]++;
16                 }
17             if(tmp[0] < tmp[1]) B |= (1<<i);
18         }
19 //        bug(1)
20         for(int i = 0; i < n; i++) A[i] ^= B;
21         for(int i = 0; i < n; i++) for(int j = i+1; j < n; j++)
22                 ans += (A[i] < A[j]);
23         return ans;
24     }
25 };
View Code

解题关键:发现异或后数的大小关系只和第k位有关。

div1

250pts

题意:字符串s(len <= 50),从中去掉K个字符之后,能否根据剩下的字符,确定去掉的是哪些字符呢。

分析:去掉K个字符后剩下的字符构成的序列如果不是原s中的唯一序列,那么就不能确定去掉的是哪K个字符,因此,可以枚举s中两个相等的字符s[i],s[j](i != j),然后

判断LCS(s[0, i-1], s[0, j-1]) + LCS(s[i+1, len-1], s[j+1, len-1]) + 1 >= len-K么,这样是能判断剩下的序列是否唯一。上面想得略复杂,题解只是判断两个相等字符之间的长度j-i<=K否,因为此时优先移走s[i],s[j]中的一个,然后将i,j之间的字符去掉,剩下的字符已经是相同的了,不论怎么取剩下的字符,最终一定不能确定被移走的是哪些字符。

代码:

 1 const int maxn = 55;
 2 char sa[maxn], sb[maxn];
 3 string str;
 4 int dp[maxn][maxn];
 5 
 6 class Decipherability {
 7 public:
 8     int len;
 9     int LCS(int n, int m, bool rev) {
10         if(n == 0 || m == 0) return 0;
11         if(rev) {
12             for(int i = n-1; i >= 0; i--)
13                 sa[n-1-i] = str[i];
14             for(int i = m-1; i >= 0; i--)
15                 sb[m-1-i] = str[i];
16         } else {
17             for(int i = 0; i < n; i++)
18                 sa[i] = str[len-n+i];
19             for(int i = 0; i < m; i++)
20                 sb[i] = str[len-m+i];
21         }
22         memset(dp, 0, sizeof dp);
23         for(int i = 1; i <= n; i++)
24             for(int j = 1; j <= m; j++)
25                 if(sa[i-1] == sb[j-1]) {
26                     dp[i][j] = dp[i-1][j-1] + 1;
27                 } else {
28                     dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
29                 }
30         return dp[n][m];
31     }
32     string check(string s, int K) {
33         str = s;
34         len = sz(s);
35         if(len == K) return "Certain";
36         int ok = 1;
37         for(int i = 0; i < len; i++)
38             for(int j = i+1; j < len; j++)if(s[i] == s[j]) {
39                     if(LCS(i, j, true)+LCS(len-i-1, len-j-1, false) >= len-K-1) {
40                         ok = 0;
41                         break;
42                     }
43                 }
44         return ok ? "Certain" : "Uncertain";
45     }
46 };
View Code

解题关键:剩下的字符构成的序列在原字符串中不唯一。

550pts

题意:div2 500pts升级版,之前已经知道B[i],B[j]的大小只和第k位(x,1-x)及C的第k位y有关。

因此当y为0,需要知道A[j]第k位为1时,i < j 且A[i]的第k位为0 的个数;当y为1,A[j]第k位为0时,i < j,且A[i] 第k位为1的个数,注意A[i],A[j]的k+1位及更高位都应该相同,这样便能够分别统计C的第k位为0,1确定的(i, j)的个数(B[i] < B[j], i < j) 。

对A[i]排序后得到A'[l, r],从最高位开始,每次统计位于A'[l,r]范围的A[j],当A[j]的第k位为1或0时,位于A[j]之前且k位 为0或1的A[i](i < j)的个数,统计后根据第k位为0 或 1,分成A'[l, mid], A'[mid+1, r]两部分继续统计第k-1位能够确定的数对(i, j)的个数。划分成A'[l, mid], A'[mid+1, r]的目的是为了保证高位都相同,进而对下一位进行统计。具体可以用树状数组实现,为了免去每次都要对数组置零的耗时,可以对数组的每个元素设一个标记。

代码:

 1 const int maxn = 131072 + 10;
 2 
 3 int order[maxn], A[maxn];
 4 LL ma[33][2];
 5 LL res[maxn][2];
 6 int cnt;
 7 
 8 bool cmp(const int &x, const int &y) {
 9     return A[x] < A[y];
10 }
11 
12 class XorSequence {
13 public:
14 
15     void add(int x, int v) {
16         while(x < maxn) {
17             if(res[x][1] != cnt) {
18                 res[x][0] = 0;
19                 res[x][1] = cnt;
20             }
21             res[x][0] += v;
22             x += lowbit(x);
23         }
24     }
25     LL query(int x) {
26         LL ans = 0;
27         while(x > 0) {
28             if(res[x][1] != cnt){
29                 res[x][0] = 0;
30                 res[x][1] = cnt;
31             }
32             ans += res[x][0];
33             x -= lowbit(x);
34         }
35         return ans;
36     }
37     void dfs(int l, int r, int dep) {
38         if(l > r || dep < 0) return;
39         int mid = l-1;
40         while(mid+1 <= r) {
41             if(!(A[order[mid+1]]&(1<<dep)))
42                 mid++;
43             else break;
44         }
45 
46         if(mid >= l && mid < r) {
47             cnt++;
48             //memset(res[dep], 0, sizeof(res[dep]));
49             for(int i = l; i <= mid; i++)
50                 add(order[i], 1);
51             for(int i = mid+1; i <= r; i++) {
52                 ma[dep][0] += query(order[i]);
53             }
54             // memset(res[dep], 0, sizeof(res[dep]));
55             cnt++;
56             for(int i = mid+1; i <= r; i++)
57                 add(order[i], 1);
58             for(int i = l; i <= mid; i++)
59                 ma[dep][1] += query(order[i]);
60         }
61         dfs(l, mid, dep-1);
62         dfs(mid+1, r, dep-1);
63     }
64     long long getmax(int N, int sz, int A0, int A1, int P, int Q, int R) {
65         A[1] = A0;
66         A[2] = A1;
67         for (int i = 3; i <= sz; i++) {
68             A[i] = (1LL*A[i - 2] * P%N + 1LL*A[i - 1] * Q%N + R) % N;
69         }
70         int n = __builtin_popcount(N-1);
71 //        for(int i = 1; i <= sz; i++)
72 //            printf("%d ", A[i]);
73 //        cout << endl;
74 
75         for(int i = 1; i <= sz; i++)
76             order[i] = i;
77         sort(order + 1, order + sz + 1, cmp);
78         cnt = 0;
79         memset(ma, 0, sizeof ma);
80         memset(res, 0, sizeof res);
81 
82         dfs(1, sz, n-1);
83         LL ans = 0;
84         for(int i = 0; i < n; i++)
85             ans += max(ma[i][0], ma[i][1]);
86         TL
87         return ans;
88     }
89 };
View Code

解题关键:排序后根据第k位对数组进行分组,统计。

850pts

题意:div2 500pt升级版,范围更大,且开始给定的n ( n <= 50)个序列,长度len及能够进行的split操作总次数<=1e9。

分析:官方题解是采用二分时间,然后在确定时间下单独考虑消除每个序列,二分需要的最少split次数,最后判断总的split次数是否<=splits。

难点在于怎么二分确定需要的最少split次数,题解里面给出了一系列证明,总结起来就是:先进行split操作,然后进行remove操作,这样结果肯定是更优的;

split的次数越多,结果更优。因此首先进行split操作,一个序列能够split成两个时,则split,如果满足split次数,能够继续将两个序列split成四个时,则split,继续split直到不能split所有序列,那么利用剩余的split次数对部分序列split,对没有split的其他部分序列进行remove操作,此时时间记做T1。剩余部分需要的时间T2和剩下的序列总长度,n'有关,即T2 = ,判断T1+T2 <= timeLimit。

代码:

 1 class CartInSupermarket {
 2 public:
 3     int calcmin(vector <int> a, int b) {
 4         int low = 1, high = (int)1e9;
 5         while(low < high) {
 6             int mid = (low+high)/2;
 7             if(possible(a, mid, b)) high = mid;
 8             else low = mid+1;
 9         }
10         return low;
11     }
12 private:
13     bool possible(vector<int> a, int timeLimit, int b) {
14         LL sum = 0;
15         for(int x: a) sum += numSplits(x, timeLimit);
16         return sum <= b;
17     }
18     int numSplits(int x, int timeLimit) {
19         int low = 0, high = x;
20         while(low < high) {
21             int mid = (low+high)/2;
22             if(possible(x, mid, timeLimit)) high = mid;
23             else low = mid + 1;
24         }
25         if(low == x) return (int)1e9 + 100;
26         return low;
27     }
28     bool possible(LL x, LL numSplits, int timeLimit) {
29         LL numParts = 1;
30         int tl = 0;
31         while(numParts <= numSplits){
32             numSplits -= numParts;
33             numParts *= 2;
34             tl++;
35         }
36         x = max(0LL, x-(numParts-numSplits));
37         return (tl + 1) + (x + numParts + numSplits - 1) / (numParts + numSplits) <= timeLimit;
38     }
39 };
View Code

解题关键:二分时间,并二分split次数,确定的splits次数下,最优化进行操作的过程。

原文地址:https://www.cnblogs.com/rootial/p/4298526.html