Preface
DP腐乳闪总出列!
(本来以为大掉分的一把,但这个号因为挺新的所以竟然还能上挺多分的,压线完成了5场上紫)
早知道去做E题了,感觉CF真得要看题目相性,有些题目就是一眼感觉不适合自己的说
A. The Good Array
一个要动点脑子的签到题,因为\(a_1,a_n\)必须等于\(1\),然后中间的\(n-1\)个元素不能出现连续的\(k\)个\(0\)
小推一下式子就是\(1+\lceil\frac{n-1}{k}\rceil\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n,k;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
scanf("%d%d",&n,&k),printf("%d\n",1+(n-1+k-1)/k);
return 0;
}
B. Lamps
这场上来给我的一个感觉就是题目好难读,难受捏
其实看清题目就很简单,因为一个灯泡爆了就不会被算到点亮的里面去
因此不难发现对于\(a_i=1\)的所有灯泡最多只能选其中一个点亮,而\(a_i=2\)的就只能选两个,依此类推
用vector
存下所有的灯泡然后排序选大的那些即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,k,x,y; vector <int> v[N]; long long ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) v[i].clear();
for (i=1;i<=n;++i) scanf("%d%d",&x,&y),v[x].push_back(y);
for (ans=0,i=1;i<=n;++i)
{
sort(v[i].begin(),v[i].end()); int sz=v[i].size();
for (j=1;j<=min(i,sz);++j) ans+=v[i][sz-j];
}
printf("%lld\n",ans);
}
return 0;
}
C. Insert Zero and Invert Prefix
刚开始想歪了走到等价转化那条路上去了,然后浪费好多时间,后面发现直接倒着构造即可
从无解的情况出发,不难发现当\(a_n=1\)是一定无解的,否则一定有解,接着考虑如何构造
考虑从后往前把序列中的一段\(1\)开头,后面跟着一段\(0\)的序列进行划分
比如对于样例1 0 0 1 1 0
,可以划分成一段1 1 0
和一段1 0 0
而这样一段序列的构造是很显然的,设这一段的长度为\(k\),且其中\(1\)的个数为\(c\)
那我们只要进行\(k-1\)次\(0\)操作,然后用一次\(c\)操作把前面的\(c\)个\(0\)反转成\(1\)即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],cnt,ans[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j,k,cnt; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
if (a[n]) { puts("NO"); continue; }
for (i=n,cnt=0;i>=1;i=k)
{
for (j=i;j>=1&&a[i]==a[j];--j);
for (k=j;k>=1&&a[j]==a[k];--k);
for (RI t=1;t<i-k;++t) ans[++cnt]=0;
ans[++cnt]=j-k;
}
for (puts("YES"),i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
}
return 0;
}
D. Ball Sorting
唉开始的基础转化就错了,导致后面一直深陷里面走不出来,本来挺简单的一个DP的说
比赛的时候思路大概是就是如果不考虑\(0\)球的个数限制,那么找出序列的LIS长度\(x\),则答案就是\(n-x\)
然后就在想如果有\(k\)个\(0\)球的限制,就会划分成\(k+1\)个段,然后贡献就是每段里面的LIS还要保证能首尾相接起来,但怎么搞都对不上样例
好家伙后面仔细一想,其实不能动的根本不是LIS,还要满足连续性,即一个极长的单调上升区间
其实也很好理解,考虑如果现在有一个\(0\)放在序列的开头,那么它至少要操作\(t\)次,满足序列剩下的\(n-t\)个数是单调升的,这样把前\(t\)个数插入进去才是对的
同理如果\(0\)在末尾的话就是找极长的单调上升前缀,而如果前后都有\(0\)的段就是找一段极长的单调上升区间
因此现在问题就转化为,把序列分为\(k+1\)段,要求一种方案使得每段内的极长单调上升区间总长度和最长,并且相邻的区间的收尾也满足单调升的条件
此时我们就很容易设计DP方程了,令\(f_{i,j}\)表示处理了前\(i\)个数,放了\(j\)个\(0\)球时最少要移动的球的数目
转移的话若\(a_i>a_{i-1}\)就直接从\(i-1\)处转移,否则枚举上一段内和它接在一起的位置\(k\)转移即可
具体实现看代码,想通了就非常简单的说,总复杂度\(O(n^3)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=505,INF=1e9;
int t,n,a[N],f[N][N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j,k,t; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (a[n+1]=INF,i=1;i<=n+1;++i) for (j=0;j<=n;++j) f[i][j]=INF;
for (i=1;i<=n+1;++i) for (j=0;j<=n;++j)
{
if (a[i-1]<a[i]) f[i][j]=f[i-1][j];
if (j) for (k=0;k<i;++k) if (a[k]<a[i]) f[i][j]=min(f[i][j],f[k][j-1]+i-k-1);
}
for (i=1;i<=n;++i) printf("%d%c",f[n+1][i]," \n"[i==n]);
}
return 0;
}
E. Decreasing Game
趣题,看似是博弈其实和博弈没太大关系,是个思博题
首先考虑如果我们存在一种划分方案,可以把\(n\)个数划分为两个集合\(A,B\),满足两个集合内元素的和相同,则此时一定后手必胜
具体的策略也很简单,若先手选择操作集合\(A\)中的数,我们就任意在集合\(B\)中取一个数即可,反之如果先手取\(B\)集合我们就取\(A\)集合
不难发现此时两个集合减去的数相同,因此一轮过后依然满足之前的性质,那么在若干轮后一定会变成两个集合中的数都是\(0\)的情况,因此是种必胜策略
那么有意思的地方就是不能划分的情况怎么处理,这里就考验人类智慧了,答案选择先手然后随便操作都能获胜(你没有看错就是随便做)
证明的话其实也很简单,假设在一轮随便操作后,使得当前局面变为了可划分成两个集合的局面,那么说明我们的策略有问题,因为此时对手作为后手可以用上面的策略击败我们
但考虑真的会出现这种局面吗,不妨设原来不存在这样一种划分方案,在一轮操作选出了数\(x,y\)(不妨设\(x\le y\))后,剩下的局面存在了可划分方案
当\(x=y\)的情况显然是矛盾的,不用考虑,因此现在只考虑\(x<y\)的情形
此时操作完会剩下一个数\(y-x\),不妨设\(y-x\)在后面的划分中被归到了集合\(A\)
设后面的划分中集合\(A\)的元素和是\(S\),那么\(A\)中其它元素的和就是\(S-(y-x)\),而\(B\)的元素和就是\(S\)
然后我们会发现如果剩下的局面有解,那么我们如果撤销对\(x,y\)的操作,把\(y\)加入集合\(A\),把\(x\)加入集合\(B\)
此时集合\(A\)的元素和就是\(S-(y-x)+y=S+x\),\(B\)的元素和就是\(S+x\),这说明原来就存在着一种划分方案,与我们的前提矛盾,因此结论成立
然后这题就做完了,确实是很有意思的说
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=305,INF=1e9;
int t,n,a[N],sum,f[N][N*N],g[N][N*N],tp[N];
int main()
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),sum+=a[i];
for (f[0][0]=1,i=0;i<n;++i) for (j=0;j<=sum;++j)
if (f[i][j]) f[i+1][j]=f[i+1][j+a[i+1]]=g[i+1][j+a[i+1]]=1;
if (sum%2||!f[n][sum/2])
{
puts("First"); fflush(stdout);
while (1)
{
for (i=1;i<=n;++i) if (a[i]>0)
{
printf("%d\n",i); fflush(stdout);
if (scanf("%d",&j),!j) return 0;
int k=min(a[i],a[j]); a[i]-=k; a[j]-=k; break;
}
}
} else
{
for (sum>>=1,i=n;i>=1;--i)
if (g[i][sum]) tp[i]=1,sum-=a[i];
puts("Second"); fflush(stdout);
while (1)
{
if (scanf("%d",&j),!j) return 0;
for (i=1;i<=n;++i) if (a[i]>0&&tp[i]!=tp[j])
{
printf("%d\n",i); fflush(stdout);
int k=min(a[i],a[j]); a[i]-=k; a[j]-=k; break;
}
}
}
return 0;
}
Postscript
现在再想是不是要再搞一个小号接着打Div2,还是去打Div1遭受折磨
因为感觉以现在的水平时而连Div2D都做不出来,去打Div1也是纯被虐
不过如果不打Div1的话不知道多久才有机会突破到橙名,也算是有点两难的说
不过话说回来,立个flag,暑假结束前打到浅橙/深橙,这样感觉明年去打区域赛心里才稍微有点底的说
标签:const,876,scanf,Codeforces,int,Div,include,RI,define From: https://www.cnblogs.com/cjjsb/p/17455998.html