暑假快乐-CF804 div2
要放假啦要放假啦要放假啦!!!!
我失去了所有的理智-----浪起来喽-----飘起来喽-----
C - The Third Problem
以下是我在考场上的错误思路,我尝试模拟1 3 7 2 5 0 6 4这个样例,并且总结了其他样例,发现第一个位置和最后一个位置不能变,0也不能变,那我就先固定了第一个,0,最后一个,再考虑剩下的有哪些位置可以填,然后又发现了一个错误结论,从0开始在1的那个方向依次寻找234……,知道换方向了,同方向找到的那些只能在有限的位置——在0和1之间01就是界限,如果超过了1的边界,0到2就是新的范围,至于方向反了的,就直接把剩下的位置全排列。总之就是先过了个样例自以为很有道理……
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <algorithm> #include <iomanip> #include <cmath> #include <cstring> #include <cstdio> #include <stdlib.h> #include <string.h> #include <set> #include <stack> #include <vector> #include <queue> typedef long long ll; const int maxn = 60; const ll mod = 1e9 + 7; int T, n, a[maxn], num[maxn]; ll ans; int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch^48); ch = getchar(); } return x * f; } int main() { T = read(); while(T--) { n = read(); ans = 1; memset(num, 0, (n+1)*sizeof(int)); for(int i=1; i<=n; i++) { a[i] = read(); } if(n <= 3) { printf("1\n"); continue; } for(int i=1; i<=n; i++) { num[a[i]] = i; //printf("num[%d] = %d\n", a[i], num[a[i]]); } int tot = num[0] - num[1], pos = 0; if(num[1] != 1 && num[1] != n) { //if(tot < 0) ans = -tot; //else ans = tot; pos++; } //printf("pos = %d\n", pos); int l = a[1], r = a[n], res = n-1; //printf("ans == %lld\n", ans); for(int i=2; i<n; i++) { int cnt = num[0] - num[i]; //printf("%d %d\n", num[0], num[i]); //printf("i = %d\n", i); //printf("cnt = %d\n", cnt); //printf("tot = %d\n", tot); pos++; //printf("pos = %d\n", pos); if(cnt < 0) { if(tot > 0) { res = i; break; } else { //printf("<>0\n"); if(i == l || i == r) { pos--; continue; } if(cnt > tot) ans = (ans*(-tot-i+1)) % mod; else { tot = cnt; ans = (ans*(-cnt-i+1)) % mod; } } //printf("ans = %lld\n", ans); } else { if(tot < 0) { res = i; break; } else { //printf("<>0\n"); if(i == l || i == r) { pos--; continue; } if(cnt < tot) ans = (ans*(tot-i+1)) % mod; else { tot = cnt; ans = (ans*(cnt-i+1)) % mod; } } //printf("ans = %lld\n", ans); } } int g = n - 3; if(l == 0 || r == 0) g++; //printf("g = %d\n", g); //printf("pos = %d\n", pos); //printf("res = %d\n", res); for(int i=res; i<n-1; i++) { if(i == l || i == r) continue; //printf("i = %d\n", i); //printf("main = %d\n", g - pos + 1); ans = (ans*(g-pos+1)) % mod; //printf("ans = %lld\n", ans); pos++; } printf("%lld\n", ans); } return 0; }
以下是正解:
按大小顺序设置一个区间,这个区间的范围是把i放进去时0~i-1的位置的最小值到最大值,所以根本就没有方向的问题,包括在区间之内的就可以在区间里任选,在区间之外就固定了,头和尾也没有什么特殊的,记得减去前面已经占上的一个位置。
如果原序列中i在0~i-1之间,在相似序列中把i放到0~i-1之间的任意一个位置首先整体的mex一定是i+1,他的子区间由于凑不成连续的就会“偏小”,导致结果一样;如果原序列中i在区间外,那么i就成了mex等于i+1的标志,它在哪里,更新之前的区间扩展到i的时刻就是区间mex值变化的时刻,往前往后都不行。好像看过题解什么都变得很显然的样子。
int main() { T = read(); while(T--) { n = read(); for(int i=1; i<=n; i++) { a[i] = read(); num[a[i]] = i; } int l = n+1, r = -1; ans = 1; for(int i=0; i<n; i++) { if(l <= num[i] && num[i] <= r) { ans = (ans*(r-l+1-i)) % mod; } l = min(l, num[i]); r = max(r, num[i]); } printf("%lld\n", ans); } return 0; }
D - Almost Triple Deletions
设f[i]为从1开始以i结尾这一段能剩下的最多相等的数,其中i是末尾,是被留下的数之一。如果在后面继续添加,从i个数加到了j个数,f[j]可以更新为f[i]+1的条件是a[j]==a[i]并且a[i]和a[j]之间的数全都可以被消去。
初始化就是i自己能不能被剩下(0~i-1能不能全消掉)。一个区间被全部消掉的条件是这个区间的长度为偶数并且这个区间中出现最多的数不超过区间长度的一半(可以等于),原因:每次消掉不同的两个数。
记录每个区间的众数用数组和max就可以做到了,每换一个起点清空一次好像也不费事。
从可以留下的数开始继续循环,更新它后面的,比较不理解的是为什么要更新到n+1再减掉不存在的那个n+1的贡献,我只知道不能从1~n取最大值,但是f[n]为什么不能直接作为答案呢?
//如果a[n]!=a[i],f[n]会因为不满足条件不被更新,最终f[n]没有值?但是它本来也就不应该有吧。
但是我要找的不是“必须剩下n最大相等的有多少”,也不是过程中的可以剩下的那个,为了得到一个可以留下的结尾,引入n+1这个特殊的任意匹配值,就可以把最优解转移过来。
如果从i到n+1中间的数消不掉呢?答案可以是相等数的个数再减去消不掉的那些,相当于替他们消了,为什么不能转移?因为这一步转移是重复操作,每找到一个可能作为答案的数就会把它循环到最后,中间被消掉的那些中就有和a[i]相等的,就和先转移后往下减一样了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <algorithm> #include <iomanip> #include <cmath> #include <cstring> #include <cstdio> #include <stdlib.h> #include <string.h> using namespace std; typedef long long ll; const int maxn = 5006; const ll mod = 1e9 + 7; int T, n, maxfreq, fr[maxn], f[maxn], a[maxn]; int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch^48); ch = getchar(); } return x * f; } void solve() { n = read(); maxfreq = 0;//出现频率最高的元素出现的次数 for(int i=1; i<=n; i++) { a[i] = read(); } f[0] = f[n+1] = 0; for(int j=1; j<=n; j++) fr[j] = 0;//记录每个元素出现的次数 for(int i=1; i<=n+1; i++)//初始化:i是奇数并且不是i的都可以删掉 { //要消掉的区间:1~i-1 if((i&1) && maxfreq<=i/2) { f[i] = 1; } else f[i] = 0; maxfreq = max(maxfreq, ++fr[a[i]]); } //为什么要记录整个序列,最后剩下的最多的数不一定是整个序列中出现最多的那一个 //为什么答案不是从前n个值中取最大值?因为所有能进行的操作必须要进行完成 //新加入的数字可能会使答案减小 for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) fr[j] = 0;//每一个区间都要重置一次 maxfreq = 0; //要消掉的区间i+1~j-1 if(f[i])//a代表[1]~a[i-1]内部可以清空,这样i才能留下作为答案 { //循环到n为什么不行?因为n不一定能留下而n+1具有特殊性质? //为什么一定要以能留下的作为结尾?因为f[j]会被更新?但即使这样也不能保证j就一定不会被消去吧 for(int j=i+1; j<=n+1; j++) { if(((j-i)&1) && maxfreq<=(j-i)/2 && (j==n+1||a[i]==a[j])) { f[j] = max(f[j], f[i]+1); } maxfreq = max(maxfreq, ++fr[a[j]]); } } } printf("%d\n", f[n+1]-1); } int main() { T = read(); while(T--) { solve(); } return 0; }