我,上紫;爱来自中国🥰
题意
有 a + b + c a+b+c a + b + c 个按钮,其中 a a a 个按钮只能小 A 按,b b b 个按钮只能小 B 按,c c c 个按钮小 A 小 B 都能按。
小 A 先手,当一个人没有按钮可按时,他就输了。
请问双方都按照最优策略行动情况下,谁会获胜?
解法
最优策略显然是先按 c c c 然后再按自己的。
所以若 c c c 是奇数,我们给 b b b 减 1 1 1 。
然后问题就转化成只有按钮 a a a 和 b b b ,小 A 先手。显然当 a > b a>b a > b 时小 A 获胜,否则小 B 获胜。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;using ll=long long ;using ull=unsigned long long ;using pii=pair<int ,int >;#define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES" ) #define NO puts("NO" ) #define Yes puts("Yes" ) #define No puts("No" ) #define errorf(...) fprintf(stderr, __VA_ARGS__) #define endl '\n' #define pb push_back int read () { int f=1 ,x=0 ;char c=getchar (); while (!isdigit (c)){if (c=='-' )f=-1 ;c=getchar ();} while (isdigit (c)){x=x*10 +c-'0' ;c=getchar ();} return x*f; }void solve (int kase) { int a=read (),b=read (),c=read (); if (c%2 ==1 )b--; if (a>b)puts ("First" ); else puts ("Second" ); }int main () { int T=read (),TT=1 ; while (T--){ solve (TT++); } return 0 ; }
B. The Walkway
题意
小 P 正在从 1 1 1 走到 n n n 。有 m m m 个卖饼干的在 s 1 , s 2 , … , s m s_1,s_2,\ldots,s_m s 1 , s 2 , … , s m ,以及常数 d d d 。
当小 P 走上一格时,只要三个条件中满足了任意一个,她就会吃一块饼干:
这是第 1 1 1 格;
这一格上有人在卖饼干;
在前 d − 1 d-1 d − 1 个格中(不包括这一格),小 P 都没有吃过饼干。
现在你可以移除恰好一个卖饼干的人,请求出小 P 最少会吃几块饼干,以及有几种移除的方案能够达到这个最小值。
解法
显然是 O ( m ) O(m) O ( m ) 模拟删除每一个人的情况。
细节较多,见代码以及注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <bits/stdc++.h> using namespace std;using ll=long long ;using ull=unsigned long long ;using pii=pair<int ,int >;#define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES" ) #define NO puts("NO" ) #define Yes puts("Yes" ) #define No puts("No" ) #define errorf(...) fprintf(stderr, __VA_ARGS__) #define endl '\n' #define pb push_back int read () { int f=1 ,x=0 ;char c=getchar (); while (!isdigit (c)){if (c=='-' )f=-1 ;c=getchar ();} while (isdigit (c)){x=x*10 +c-'0' ;c=getchar ();} return x*f; }void solve (int kase) { int n=read (),m=read (),d=read (); vector<int > sellers; sellers.push_back (1 ); bool have1 = 0 ; for (int i=0 ;i<m;i++){ int a=read (); if (a!=1 )sellers.push_back (a); else have1=1 ; } int res = sellers.size (); sellers.push_back (n+1 ); for (int i=0 ;i<sellers.size ()-1 ;i++){ res += (sellers[i+1 ]-sellers[i]-1 )/d; } int ans,cnt; if (have1)ans=res,cnt=1 ; else ans=INT_MAX; for (int i=1 ;i<sellers.size ()-1 ;i++){ int k = res; k --; k -= (sellers[i]-sellers[i-1 ]-1 )/d; k -= (sellers[i+1 ]-sellers[i]-1 )/d; k += (sellers[i+1 ]-sellers[i-1 ]-1 )/d; if (k<ans){ ans = k; cnt=1 ; } else if (k==ans)cnt++; } printf ("%d %d\n" ,ans,cnt); }int main () { int T=read (),TT=1 ; while (T--){ solve (TT++); } return 0 ; }
C. Yet Another Permutation Problem
题意
给定 n n n ,求出一个排列,使得 n n n 对相邻数(第一个和最后一个视为相邻)的最大公约数不同数量最大。
【加强版】洛谷P9345 夕阳西下几时回
解法
首先我们知道答案的上界为 ⌊ n 2 ⌋ \left\lfloor\dfrac{n}{2}\right\rfloor ⌊ 2 n ⌋ ,这是因为对于两个不同的数,想要获得最大公约数为 d d d ,则两个数至少为 d , 2 d d,2d d , 2 d ,显然对大于 ⌊ n 2 ⌋ \left\lfloor\dfrac{n}{2}\right\rfloor ⌊ 2 n ⌋ 的数是不可能的。
然后这个上界总是能取到,只要采用如下构造方式:
对于 i = 1 ∼ ⌊ n 2 ⌋ i=1\sim\left\lfloor\dfrac{n}{2}\right\rfloor i = 1 ∼ ⌊ 2 n ⌋ ,将 i i i 和 2 i 2i 2 i 安排相邻即可。
如代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> using namespace std;using ll=long long ;using ull=unsigned long long ;using pii=pair<int ,int >;#define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES" ) #define NO puts("NO" ) #define Yes puts("Yes" ) #define No puts("No" ) #define errorf(...) fprintf(stderr, __VA_ARGS__) #define endl '\n' #define pb push_back int read () { int f=1 ,x=0 ;char c=getchar (); while (!isdigit (c)){if (c=='-' )f=-1 ;c=getchar ();} while (isdigit (c)){x=x*10 +c-'0' ;c=getchar ();} return x*f; }const int N = 1e5 +5 ;bool in[N]; void solve (int kase) { int n=read (); for (int i=1 ;i<=n;i++)in[i]=0 ; vector<int > arr; for (int i=1 ;i<=n/2 ;i++){ if (i%2 ==0 )continue ; arr.pb (i);in[i]=1 ; arr.pb (i*2 );in[i*2 ]=1 ; int k=i; while (k*4 <=n){ k*=2 ; arr.pb (k*2 ); in[k*2 ]=1 ; } } for (int i=1 ;i<=n;i++){ if (!in[i])arr.pb (i); } for (int x:arr){ printf ("%d " ,x); } puts ("" ); }int main () { int T=read (),TT=1 ; while (T--){ solve (TT++); } return 0 ; }
D. Trees and Segments
题意
给定一个 0 − 1 0-1 0 − 1 序列,长度为 n n n ,你可以翻转不超过其中 k k k 位。
设 L 0 L_0 L 0 为最长连续 0 0 0 长度(没有 0 0 0 则 L 0 = 0 L_0=0 L 0 = 0 ),L 1 L_1 L 1 为最长连续 1 1 1 长度(没有 1 1 1 则 L 1 = 0 L_1=0 L 1 = 0 ),
请对于 a = 1 ∼ n a=1\sim n a = 1 ∼ n ,分别求出一种翻转方案,使得 a ⋅ L 0 + L 1 a\cdot L_0 + L_1 a ⋅ L 0 + L 1 最大。
输出这 n n n 个最大值。
n ≤ 3000 n\le 3000 n ≤ 3000 。
解法
显然复杂度为 O ( n 2 ) O(n^2) O ( n 2 ) 。
我们考虑枚举所有可能的 L 0 L_0 L 0 的情况下,最大的 L 1 L_1 L 1 是多少。
如何枚举呢?
对于每个 L 0 L_0 L 0 的长度,我们枚举所有取到 L 0 L_0 L 0 的区间的左端点。这个枚举是 O ( n 2 ) O(n^2) O ( n 2 ) 的。通过预先计算一次前缀和,我们可以 O ( 1 ) O(1) O ( 1 ) 求出如果要使这一段取到 L 0 L_0 L 0 需要翻转几位。如果翻转的位数大于 k k k ,那么舍去,否则我们就用剩下的次数在一个前缀和一个后缀中找出使用 p p p 次翻转最长的 1 1 1 的长度,而这个问题可以动态规划 O ( n 2 ) O(n^2) O ( n 2 ) 解决。
求出之后只需要对每一个 L 0 L_0 L 0 和 a a a 分别枚举,就可以再 O ( n 2 ) O(n^2) O ( n 2 ) 一次求出答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <bits/stdc++.h> using namespace std;using ll=long long ;using ull=unsigned long long ;using pii=pair<int ,int >;#define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES" ) #define NO puts("NO" ) #define Yes puts("Yes" ) #define No puts("No" ) #define errorf(...) fprintf(stderr, __VA_ARGS__) #define endl '\n' #define pb push_back int read () { int f=1 ,x=0 ;char c=getchar (); while (!isdigit (c)){if (c=='-' )f=-1 ;c=getchar ();} while (isdigit (c)){x=x*10 +c-'0' ;c=getchar ();} return x*f; }const int N = 3005 ;char a[N];int s[N];int dp1[N][N],dp1mx[N][N];int dp2[N][N],dp2mx[N][N];int mxVal[N];int ans[N];void solve (int kase) { int n=read (),k=read (); scanf ("%s" ,a+1 ); for (int i=1 ;i<=n;i++){ a[i]-='0' ; s[i]=s[i-1 ]+a[i]; } for (int i=0 ;i<=n+1 ;i++){ for (int j=0 ;j<=n+1 ;j++){ dp1[i][j]=dp1mx[i][j]=0 ; dp2[i][j]=dp2mx[i][j]=0 ; } } for (int i=0 ;i<=n+1 ;i++){ mxVal[i]=ans[i]=INT_MIN; } for (int i=1 ;i<=n;i++){ for (int j=0 ;j<=n;j++){ if (j==0 ){ if (a[i]==1 )dp1[i][j]=1 +dp1[i-1 ][j]; else dp1[i][j]=0 ; } else { dp1[i][j]=dp1[i][j-1 ]; if (a[i]==1 )dp1[i][j]=max (dp1[i][j],dp1[i-1 ][j]+1 ); else dp1[i][j]=max (dp1[i][j],dp1[i-1 ][j-1 ]+1 ); } dp1mx[i][j]=max (dp1mx[i-1 ][j],dp1[i][j]); } } for (int i=n;i>=1 ;i--){ for (int j=0 ;j<=n;j++){ if (j==0 ){ if (a[i]==1 )dp2[i][j]=1 +dp2[i+1 ][j]; else dp2[i][j]=0 ; } else { dp2[i][j]=dp2[i][j-1 ]; if (a[i]==1 )dp2[i][j]=max (dp2[i][j],dp2[i+1 ][j]+1 ); else dp2[i][j]=max (dp2[i][j],dp2[i+1 ][j-1 ]+1 ); } dp2mx[i][j]=max (dp2mx[i+1 ][j],dp2[i][j]); } } mxVal[0 ]=dp1mx[n][k]; for (int len=1 ;len<=n;len++){ for (int i=1 ;i+len-1 <=n;i++){ int needcnt = (s[i+len-1 ]-s[i-1 ]); if (needcnt>k)continue ; int remain = k-needcnt; mxVal[len] = max ({mxVal[len],dp1mx[i-1 ][remain],dp2mx[i+len][remain]}); } } for (int len=0 ;len<=n;len++){ for (int f=1 ;f<=n;f++){ ans[f]=max (ans[f],f*len+mxVal[len]); } } for (int i=1 ;i<=n;i++){ printf ("%d " ,ans[i]); } puts ("" ); }int main () { int T=read (),TT=1 ; while (T--){ solve (TT++); } return 0 ; }
E2. Rollbacks (Hard Version)
题意
维护一个数据结构,支持:
末尾添加一个数;
删除末尾的 k k k 个数;
查询序列中不同的数的个数;
撤销上一次修改操作;
强制在线 。
解法
考虑用 1 0 6 10^6 1 0 6 个 set
维护每个数现在出现的所有位置。
使用树状数组维护不同的数的个数,只有每个数第一次出现的位置是 1 1 1 ,其他是 0 0 0 。
接下来考虑撤销操作。我们用一个数组存目前的序列,但是对于删除不会真的删除,而是直接把代表数组尾的指针前移即可。这样就保留了删除的信息,那么就可以进行进行撤销了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #include <bits/stdc++.h> using namespace std;using ll=long long ;using ull=unsigned long long ;using pii=pair<int ,int >;#define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES" ) #define NO puts("NO" ) #define Yes puts("Yes" ) #define No puts("No" ) #define errorf(...) fprintf(stderr, __VA_ARGS__) #define endl '\n' #define pb push_back int read () { int f=1 ,x=0 ;char c=getchar (); while (!isdigit (c)){if (c=='-' )f=-1 ;c=getchar ();} while (isdigit (c)){x=x*10 +c-'0' ;c=getchar ();} return x*f; }const int N = 1e6 +6 ;int t[N];int lowbit (int x) { return x&(-x); }int query (int i) { int res = 0 ; for (;i;i-=lowbit (i)){ res += t[i]; } return res; }void add (int i,int k) { for (;i<N;i+=lowbit (i)){ t[i]+=k; } } set<int > id[N];int a[N],s[N];int ptr=0 ;void erase (int pos) { if (a[pos]==-1 )return ; if (id[a[pos]].size ()){ add (*id[a[pos]].begin (),-1 ); id[a[pos]].erase (pos); } if (id[a[pos]].size ()){ add (*id[a[pos]].begin (),1 ); } }void insert (int pos) { if (a[pos]==-1 )return ; if (id[a[pos]].size ()){ add (*id[a[pos]].begin (),-1 ); } id[a[pos]].insert (pos); if (id[a[pos]].size ()){ add (*id[a[pos]].begin (),1 ); } }int main () { memset (a,-1 ,sizeof (a)); int q=read (); stack<pii> changes; while (q--){ char op[2 ]; scanf ("%s" ,op); if (op[0 ]=='+' ){ int x=read (); int mem = a[ptr+1 ]; erase (ptr+1 ); a[ptr+1 ]=x; insert (ptr+1 ); ptr++; changes.push ({1 ,mem}); } else if (op[0 ]=='-' ){ int k=read (); ptr -= k; changes.push ({-1 ,k}); } else if (op[0 ]=='?' ){ printf ("%d\n" , query (ptr)); } else { auto [u,v]=changes.top ();changes.pop (); if (u==1 ){ erase (ptr); a[ptr]=v; insert (ptr); ptr--; } else if (u==-1 ){ ptr+=v; } } fflush (stdout); } return 0 ; }