《Educational Codeforces Round 111 (Rated for Div. 2)》
A:dp处理一下:dp[i][j] - 以j结尾,和为i的最小长度。
转移很简单。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 1e3 + 5; const int M = 2e3 + 5; const LL Mod = 4294967296; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; int dp[5005][5005];//dp[i][j] - 以j结尾,和为i的最小长度 void init() { memset(dp,0x3f3f3f,sizeof(dp)); dp[1][1] = 1; for(int i = 2;i <= 5000;++i) { for(int j = 1;j <= 5000;++j) { if(i - j >= 0) dp[i][j] = min(dp[i][j],dp[i - j][j] + 1); if(i - j >= 0) dp[i][j] = min(dp[i][j],dp[i - j][j - 1] + 1); if(i - j >= 0 && j - 2 >= 0) dp[i][j] = min(dp[i][j],dp[i - j][j - 2] + 1); } } } int main() { init(); int ca;scanf("%d",&ca); while(ca--) { int n;scanf("%d",&n); int mi = INF; for(int i = 1;i <= 5000;++i) mi = min(mi,dp[n][i]); printf("%d\n",mi); } // system("pause"); return 0; }
B:可以发现,a的贡献是固定的即n * L.
这里对答案有影响的就是b的贡献。
我们对连续的一段缩点,可以发现,如果b >= 0,我们要最大化b的代价就是一个个删除。
b < 0,就需要一段段去删,去奇数对称和偶数对称讨论之后可以发现贡献都是一样的。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 2e5 + 5; const int M = 2e3 + 5; const LL Mod = 4294967296; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; int main() { int ca;scanf("%d",&ca); while(ca--) { int n,a,b;scanf("%d %d %d",&n,&a,&b); string s;cin >> s; int cnt = 1; char c = s[0]; for(int i = 1;i < s.size();++i) { if(s[i] == c) continue; else { ++cnt; c = s[i]; } } LL sum = n * a; if(b >= 0) sum += n * b; else sum += (cnt / 2 + 1) * b; printf("%lld\n",sum); } // system("pause"); return 0; }
C:从图形上看,对于递增的三个x坐标,i,j,k。
如果a[j] 在 a[i] 和 a[k]围成的长方形里,肯定就满足这个情况,因为横纵距离都刚好是相加。
并且除了这种情况之外,别的情况都无法再形成。
即两种情况。a[i] <= a[j] <= a[k],或者 a[i] >= a[j] >= a[k]。
我们单调栈维护一下左右最近的 <= 和 >= 位置。
但是这里要算贡献,还不行,我们可以发现,我们只是处理除了每段最小的坏区间。
还有很多的搭配没被计算到。
这里我们有一种思路就是如果坏区间是[L,r]。那么我们就更新区间[1,L]里的mi = r。一直维护这个最小值。
这点可以用线段树做到,这样我们最后就可以计算以每个i点为左端点的区间合法区间数量。复杂度(O(nlogn)
但是我们也可以从右往左维护合法区间。就是不断缩小不合法区间的右端点,并且因为从右往左,这个区间肯定包含在当前里且 >= 当前的坐标。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 2e5 + 5; const int M = 2e3 + 5; const LL Mod = 4294967296; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; int a[N],L[N],r[N],Q[N],S[N],mi[N],le[N],ri[N]; int main() { int ca;scanf("%d",&ca); while(ca--) { int n;scanf("%d",&n); for(int i = 1;i <= n;++i) scanf("%d",&a[i]),L[i] = r[i] = le[i] = ri[i] = 0,mi[i] = INF; int top = 0,tot = 0; Q[++top] = 1; S[++tot] = 1; for(int i = 2;i <= n;++i) { while(top != 0 && a[i] < a[Q[top]]) --top; if(top != 0) L[i] = Q[top]; Q[++top] = i; while(tot != 0 && a[i] > a[S[tot]]) --tot; if(tot != 0) le[i] = S[tot]; S[++tot] = i; } top = tot = 0; Q[++top] = n; S[++tot] = n; for(int i = n - 1;i >= 1;--i) { while(top != 0 && a[i] > a[Q[top]]) --top; if(top != 0) r[i] = Q[top]; Q[++top] = i; while(tot != 0 && a[i] < a[S[tot]]) --tot; if(tot != 0) ri[i] = S[tot]; S[++tot] = i; } for(int i = 1;i <= n;++i) { if(L[i] == 0 || r[i] == 0) continue; mi[L[i]] = min(mi[L[i]],r[i]); } for(int i = 1;i <= n;++i) { if(le[i] == 0 || ri[i] == 0) continue; mi[le[i]] = min(mi[le[i]],ri[i]); } LL ans = 0; int pre = n + 1; for(int i = n;i >= 1;--i) { pre = min(pre,mi[i]); ans += (pre - i); } printf("%lld\n",ans); } //system("pause"); return 0; }
D:我们对题目中的式子转化一下,假设有一个数组b,满足bi + i = ai。
那么ai + aj = bi + i + bj + j = i + j 即 bi = -bj。
也就是对于数组b,这两个数的值相反。为了最大化这个组数,我们对半分配一样的值k,一半b = k,一半b = -k,对于奇数个时,有两种情况:
1:n / 2 = k,n / 2 + 1 = -k.
2: n / 2 + 1 = k,n / 2 = -k;
然后我们考虑怎么去分配这个值,对于区间[L,r]。
首先,我们可以发现,对于每个a的值都是i - k或者i + k,这个值显然不满足ai = i,所以第一个条件恒成立。
我们可以把区间分成三段[1,x],[x,y],[y,n]。
当满足1 - k >= L && n + k <= r 即k <= min(1 - L,r - n)时,对于每个值都可以取正和负。
那么对于每个k就有C(n,n / 2)种方案。
当k > min(1 - L,r - n)时,假设lf = max(1,L + k),rg = min(n,r - k).
对于[1,lf)的,显然只能取i + k,对于(rg,n],显然只能取i - k,然后再对中间的都可以取。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 2e5 + 5; const int M = 2e3 + 5; const LL Mod = 1e9 + 7; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; LL f[N],inv[N]; LL quick_mi(LL a,LL b) { LL re = 1; while(b) { if(b & 1) re = re * a % Mod; a = a * a % Mod; b >>= 1; } return re; } void init() { f[0] = 1; for(int i = 1;i < N;++i) f[i] = f[i - 1] * i % Mod; inv[N - 1] = quick_mi(f[N - 1],Mod - 2); for(int i = N - 2;i >= 0;--i) inv[i] = inv[i + 1] * (i + 1) % Mod; } LL C(LL n,LL m) { if(n < m || m < 0) return 0; return f[n] * inv[n - m] % Mod * inv[m] % Mod; } int main() { init(); int ca;scanf("%d",&ca); while(ca--) { int n,L,r;scanf("%d %d %d",&n,&L,&r); int limit = min(1 - L,r - n); LL ans = C(n,n / 2) * limit % Mod; if(n % 2 == 0) { for(int k = limit + 1;;++k) { int lf = max(1,L + k),rg = min(n,r - k); if(rg - lf + 1 < 0) break; ans = (ans + C(rg - lf + 1,n / 2 - lf + 1)) % Mod; } } else { ans = (ans + C(n,n / 2 + 1) * limit % Mod) % Mod; for(int k = limit + 1;;++k) { int lf = max(1,L + k),rg = min(n,r - k); if(rg - lf + 1 < 0) break; if(n / 2 - lf + 1 >= 0) ans = (ans + C(rg - lf + 1,n / 2 - lf + 1)) % Mod; if(n / 2 + 1 - lf + 1 >= 0) ans = (ans + C(rg - lf + 1,n / 2 + 1 - lf + 1)) % Mod; } } printf("%lld\n",ans); } //system("pause"); return 0; }
E:
二分答案.
这里唯一的标准就是我们每个块在字符串中的顺序是固定的,所以我们可以状压块进入的顺序。
对于一个子集s,如果它不包含块c,那么就说明s | c,就代表s后面接的块是c,那么我们就需要预处理出pos[i][j]表示i位置后面块j结尾的最小位置。
因为我们用最小的取维护肯定是最优的,对于这个位置的寻找,官方题解里用了一个非常巧妙的正反迭代,对于现在的长度是d,i + d,里面没有别的字符的位置,那就说明这段可以变成连续的j。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<LL,int> pii; const int N = 2e5 + 5; const int M = 2e3 + 5; const LL Mod = 1e9 + 7; #define pi acos(-1) #define INF 1e9 #define dbg(ax) cout << "now this num is " << ax << endl; string s; int n,k,lst[20],dp[1 << 18],pos[N][20]; bool check(int x) { fill(pos[0],pos[0] + N * 20,n + 1); fill(lst,lst + 20,n + 1); fill(dp,dp + (1 << 18),n + 1); //printf("x is %d\n",x); for(int i = n;i >= 1;--i) { if(s[i - 1] != '?') lst[s[i - 1] - 'a'] = i; int mix = n + 1; for(int j = 0;j < k;++j) { int nxt = i + x - 1; if(nxt < mix) pos[i][j] = nxt; else pos[i][j] = pos[i + 1][j]; mix = min(mix,lst[j]); } mix = n + 1; for(int j = k - 1;j >= 0;--j) { int nxt = i + x - 1; if(nxt >= mix) pos[i][j] = pos[i + 1][j]; mix = min(mix,lst[j]); } //for(int j = 0;j < k;++j) printf("dp[%d][%d] is %d\n",i,j,pos[i][j]); } dp[0] = 0; for(int i = 0;i < (1 << k);++i) { for(int j = 0;j < k;++j) { int g = ((i >> j) & 1); if(g == 1) continue; if(dp[i] < pos[dp[i] + 1][j]) { dp[i | (1 << j)] = min(dp[i | (1 << j)],pos[dp[i] + 1][j]); } } } return dp[(1 << k) - 1] < n + 1; } int main() { scanf("%d %d",&n,&k); cin >> s; int L = 1,r = n,ans = 0; while(L <= r) { int mid = (L + r) >> 1; if(check(mid)) L = mid + 1,ans = mid; else r = mid - 1; } printf("%d\n",ans); // system("pause"); return 0; }