Educational Codeforces Round 97 div2
前言
我的天啊,上次刚说有个简单的,现在这场比赛就打到我心态爆炸QAQ。
上次还能勉强五五开,这次ZWQ和CLB直接把我摁在地上锤了QAQ,QAQ果然我只能做简单题吗QAQ。
A
题意:相当于给你一个区间\([l,r]\),要求选择一个数字\(a\)使得\(∀x∈[l,r],x\mod a≥\frac{a}{2}\)。
题解:看样例,提示的十分明显,\([3,4]\)选\(5\),不妨考虑\(a=r+1\),这样的话区间只要\(l≥\frac{r+1}{2}\)即可,但是呢,为什么不存在比这个更加优秀的呢?不难发现,如果\(a>r\),那么\(a\)越大,\(l\)的下限也越大,\(a=r+1\)是最优秀的。
在考虑\(a≤r\),不难发现,\(l≥\left \lfloor \frac{r}{a} \right \rfloor*a\)(不是严格下限)
当\(a≥\left \lfloor \frac{r+2}{2} \right \rfloor\),\(l≥a≥\left \lfloor \frac{r+2}{2} \right \rfloor≥\frac{r+1}{2}\),当\(a<\left \lfloor \frac{r+2}{2} \right \rfloor\),因为\(l\)的下限要小于\(l≥\frac{r+1}{2}\),所以\([\left \lceil \frac{r+1}{2} \right \rceil,r]\)区间内不存在任何被\(a\)整除的数字,所以\(a>r-\left \lceil \frac{r+1}{2} \right \rceil≥r-\frac{r+1}{2}≥\frac{r-1}{2}≥\left \lfloor \frac{r-1}{2} \right \rfloor\),所以\(a≥\left \lfloor \frac{r+1}{2} \right \rfloor\)。
因此只考虑\(a=\left \lfloor \frac{r+1}{2} \right \rfloor\)的情况,不难发现,当\(r\)为奇数时,\(\left \lfloor \frac{r+1}{2} \right \rfloor=\left \lfloor \frac{r+2}{2} \right \rfloor\),不成立,因此\(r\)为偶数,此时\(a=\frac{r}{2}\),\(r\mod a≡0\),不成立。
时间复杂度:\(O(1)\)
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int l,r;scanf("%d%d",&l,&r);
int mid=r+1;
if(l>=(mid/2+(mid&1)))printf("YES\n");
else printf("NO\n");
}
return 0;
}
B
题意:给你一串\(01\)字符串,长度为\(n\),\(n\)为\(偶数\),其中一半为\(0\),一半为\(1\),然后每次操作可以选择\([l,r]\)翻转,问最少几次操作可以变成\(s_i≠s_{i+1}\)的01串,即:\(01010101...\)或者\(10101010101...\)
题解:只有两个标准字符串,不妨考虑先转化成\(01010101...\),我们称这个为标准字符串,记为\(B\),原字符串为\(A\),建立新的字符串:\(C\),\(C_{i}=[B_{i}≠A_{i}]\)。
不难发现,对于\(C\)中的字符串,选择\([l,r]\)进行翻转,\(r-l+1\)为偶数时,翻转完之后还要对此区间取反,而奇数时便是单纯的翻转。
把一段连续的\(1\)成为联通块。
分几种情况讨论不难证明每次翻转最多消除一个联通块,所以答案\(≥\)联通块数量。
那么现在构造一种方案等于联通块数量。
如果一个联通块的,长度为偶数,直接翻转,这样这会剩下偶数个奇数长度的联通块。
然后对于相邻的两个联通块,如果中间\(0\)的个数为偶数个,则将一个联通块和中间的\(0\)翻转,使两个联通块合并成偶数联通块并且一次消除掉。
但是如果相邻的联通块中间都是奇数个\(0\)呢?其实通过构造\(C\)的方法以及\(0,1\)个数相同,我们不难得到一个结论,奇数位的\(1\)等于偶数位的\(1\),因此不存在相邻联通块中间都是奇数个\(1\)的情况。
证毕。
然后只要两个标准字符串都搞一遍就可以了。
时间复杂度:\(O(n)\)
因为他们做完后都在说做法,搞得我总感觉不是我自己独立做出来的
#include<cstdio>
#include<cstring>
#define N 110000
using namespace std;
inline int mymin(int x,int y){return x<y?x:y;}
inline int mymax(int x,int y){return x>y?x:y;}
int a[N],b[N],n;
char st[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
scanf("%s",st+1);
for(int i=1;i<=n;i++)a[i]=st[i]-'0';
int ans,sum=0;
for(int i=1;i<=n;i++)b[i]=((a[i]&1)==(i&1));
for(int i=1;i<=n;i++)
{
if(b[i]==1 && b[i-1]==0)sum++;
}
ans=sum;
for(int i=1;i<=n;i++)b[i]=((a[i]&1)!=(i&1));
sum=0;
for(int i=1;i<=n;i++)
{
if(b[i]==1 && b[i-1]==0)sum++;
}
ans=mymin(sum,ans);
printf("%d\n",ans);
}
return 0;
}
C
给你一个数组\(a\),满足\(1≤a[i]≤n\),然后你需要构造一个正整数数组\(b\),其中每个数字都不相同,然后使得\(\sum\limits_{i=1}^n|b[i]-a[i]|\)最小,问这个值最小是多少。
做法:把\(a\)排序,不难发现,\(b_{n}≤2n\)(事实上,同机房大佬说\(\frac{3n}{2}\)就够了,想想也是),\(b_{i}<b_{i+1}\)。
然后跑个\(n^3\)的DP即可,事实上,可以优化到\(O(n^2)\)。
时间复杂度:\(O(n^3)\)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 410
using namespace std;
int dp[N][N],n,a[N];
inline int zabs(int x){return x<0?-x:x;}
inline int mymin(int x,int y){return x<y?x:y;}
inline bool cmp(int x,int y){return x<y;}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1,cmp);
int limit=2*n;
for(int i=1;i<=limit;i++)dp[1][i]=zabs(a[1]-i);
for(int i=2;i<=n;i++)
{
for(int j=1;j<=limit;j++)
{
dp[i][j]=999999999;
for(int k=j-1;k>=1;k--)dp[i][j]=mymin(dp[i-1][k]+zabs(a[i]-j),dp[i][j]);
}
}
int ans=999999999;
for(int i=1;i<=limit;i++)ans=mymin(ans,dp[n][i]);
printf("%d\n",ans);
}
return 0;
}
D
题意:给你一个BFS遍历顺序,要求你按照这个顺序找到满足要求的树中高度最小的,输出最小高度,满足对于一个点,会直接将其儿子全部加入到队列中(也就是在BFS顺序中是连续一段的),且儿子被丢进去的顺序是按照儿子的编号从小到大丢的,根固定为\(1\),高度为\(0\)。
做法:我们将顺序中每个递增的连续一小段称为联通块。
不难发现,每个儿子接一个联通块是最优秀的(不难发现,如果只接一般的联通块,一定不会比这种方法优秀),所以下一层的点数一定大于等于这一层的点数。
所以贪心做一下就行了。
#include<cstdio>
#include<cstring>
#define N 210000
using namespace std;
int n,a[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int now=1,used=0,tot=0,h=0,pre=0;
for(int i=2;i<=n;i++)
{
if(a[i]>pre)pre=a[i],tot++;
else
{
used++;pre=a[i];
if(used==now)
{
h++;used=0;
now=tot;tot=1;
}
else tot++;
}
}
if(n>1)h++;
printf("%d\n",h);
}
return 0;
}
E
题意:给你\(a,b\)数组,规定一个操作:\(x,i\)为\(a[x]=i\)且\(x\)不在\(b\)数组中,\(b\)数组严格递增且范围在\([1,n]\)中,长度为\(k\),\(a\)数组长度为\(n\)。
然后问你能不能通过最少的操作数把\(a\)数组变成严格递增,不能输出\(-1\),能输出最小操作数。
做法:额,首先根据\(b\)数组分成\(k+1\)块,经CLB提示,为了代码方便,会在数组两端加入哨兵点,顺利的把\(a\)数组禁锢在\(b\)数组中(就是\(b[0]=0,b[k+1]=n+1\),同时\(a\)数组做出类似的变化)。
如果\(a_{b_{i}}-a_{b_{i-1}}-1<b_{i}-b_{i-1}-1\)就输出\(-1\)。
然后考虑对于每一段跑\(DP\)求出最小的操作数变成递增的,\(dp[i]\)为\(i\)不改变时前面变成递增的最小操作数。
\(dp[i]=min(dp[j]+i-j-1)(a[i]-a[j]-1≥j-i-1)\)
化简一下条件:\(a[i]-j≥a[i]-i\)。
然后化简一下转移:\(dp[i]-i=min(dp[j]-j-1)\)。
然后这个用线段树维护一下就行了,顺便注意一下细节。(当然,同机房大佬也有用树状数组的,还有师兄用类似的方法直接求了最长上升子序列,应该也是同样的原理)
时间复杂度:\(O(nlogn)\)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510000
#define SN 11000000
using namespace std;
inline int mymin(int x,int y){return x<y?x:y;}
struct node
{
int l,r,c;
}tr[SN];int len,rt;
inline int addnode(){len++;tr[len].l=tr[len].r=tr[len].c=0;return len;}
inline void updata(int x){tr[x].c=mymin(tr[tr[x].l].c,tr[tr[x].r].c);}
inline void link(int &x,int l,int r,int k,int c)
{
if(!x)x=addnode();
if(l==r){tr[x].c=c;return ;}
int mid=(l+r)>>1;
if(k<=mid)link(tr[x].l,l,mid,k,c);
else link(tr[x].r,mid+1,r,k,c);
updata(x);
}
inline int findans(int x,int l,int r,int c/*小于等于k的*/)
{
if(r<=c)return tr[x].c;
if(!x)return 0;
int mid=(l+r)>>1;
if(mid>=c)return findans(tr[x].l,l,mid,c);
else return mymin(findans(tr[x].l,l,mid,c),findans(tr[x].r,mid+1,r,c));
}
int a[N],b[N];
int id[N],cnt/*离散化*/,be[N],dp[N];
int n,k;
inline bool cmp(int x,int y){return a[x]<a[y];}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]-=i;
id[i]=i;
}
sort(id+1,id+n+1,cmp);
for(int i=1;i<=n;i++)
{
if(a[id[i]]!=be[cnt])cnt++,be[cnt]=a[id[i]];
a[id[i]]=cnt;
}
for(int i=1;i<=k;i++)scanf("%d",&b[i]);
for(int i=2;i<=k;i++)
{
if(a[b[i]]<a[b[i-1]])
{
printf("-1\n");
return 0;
}
}
//b[0]=0,a[0]=0;
cnt++;b[++k]=n+1;a[n+1]=cnt;//哨兵节点
int ans=0;
for(int i=1;i<=k;i++)
{
len=rt=0;//清除掉整棵树
int l=b[i-1]+1,r=b[i],limit=a[l-1]/*大于这个数字才有资格成为不减的象征*/;
link(rt,0,cnt,limit,-b[i-1]-1/*十分的有诱惑性*/);
for(int j=l;j<=r;j++)
{
if(a[j]>=limit)
{
dp[j]=findans(rt,0,cnt,a[j])+j;
link(rt,0,cnt,a[j],dp[j]-j-1);
}
}
ans+=dp[r];
}
printf("%d\n",ans);
return 0;
}
F
题意:现在有\(n\)个数字的数组\(a\),要求你求满足要求的排列\(b\)的数量。
要求:对于\(b[i]\)而言,设\(y=max(a[b[j]])(j<i)\),那么要求\(a[b[i]]≥2y\)或者\(2a[b[i]]≤y\)
做法:艹,看错题了,不然超级弱智,也就G不会了
先把\(a\)排序一下。
设\(f[i][j]\)为最大值为\(a[i]\),填了\(j\)个数字的方案数,然后暴力转移。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5100
using namespace std;
typedef long long LL;
LL dp[N][N],f[N],mod=998244353;
int n,a[N],id[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
int l=0;
for(int i=1;i<=n;i++)
{
while(a[l+1]<=a[i]/2)l++;
id[i]=l;
f[i]=1;
}
f[0]=1;
for(int i=1;i<=n;i++)//一层一层往上DP
{
for(int j=1;j<=n;j++)
{
if(id[j]+1<i)dp[i][j]=0;
else dp[i][j]=(f[id[j]]+dp[i-1][j]*(id[j]-i+2))%mod;
}
f[0]=0;for(int j=1;j<=n;j++)f[j]=(f[j-1]+dp[i][j])%mod;
}
printf("%lld\n",dp[n][n]);
return 0;
}
G
题意:给你\(n\)个字符串,每个字符串有个\(a\)值,有两种操作。
- 修改第\(i\)个字符串的\(a\)值。
- 给你一个字符串\(T\),问你\(min(a_{i})(S_{i}是T的字串)\)
初始给出的字符串长度总和\(3e5\),询问的字符串长度总和\(3e5\)。
做法:感谢同机房大佬提供的思路(我目前只停留在口胡阶段),Orz。
以前一直以为处理子串信息只能用后缀自动机。
事实上,我现在也这么认为的。
但是大佬提供了一种新式思考,因为字符串总和\(3e5\),所以不妨处理\(T_{i}\)(\(T_{i}\)为\(T\)字符串中\(1\)~\(i\)的字符组成的字符串)的后缀的值。
那什么可以快速处理一个字符串的后缀呢?\(AC\)机啊。
我们不妨考虑建一棵树,树上的点就是开始给出的\(n\)个点,一个点的儿子的后缀就是这个点的字符串,如果名字相同,则编号大的为儿子(因此,在\(AC\)机中,每个点如果对应多个编号,直接用编号大的),然后这个树用树剖维护。
而这个树,可以用\(AC\)机的\(last\)指针快速建出来。
然后对于每个询问,一个字符一个字符跑\(AC\)机,然后对于每个字符跑完后的位置所对应的\(last\),求一下\(last\)在我们新建的树中到根节点的路径上的最小值。
时间复杂度:\(O((n+q)log^2n)\)。(但是常数非常的小)
我只会Orz,QAQ。
口胡没有代码。