Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)
A直接速通就好了,我第一眼看的时候以为是整个数组可以有重复的数字,后面发现是保证没有的,所以直接随便交换一下位置就结束了。
B,很经典的CF题,随便推导一下就能够发现,如果我想要一个位置能够发生变化,那么唯一的要求就是他以及他前面的位置有1出现过。而且完全不需要考虑为了一个数字变化而引起的其他位置的变化无法解决,如果这个位置原本是0变成了1,那直接让它异或一次他本身即可,如果是1变成了0,那么如果前面有1存在,就是重复操作就好了。所以我们只需要找到第一个需要改变的位置,然后看是否有1已经在这个位置或之前出现过就可以判断答案。
C,一个简单的dp,\(f[i]\)表示从1到\(i\)的位置,有多少个位置能够满足从这个位置开始一直累加到\(i\)这个位置,在累加完\(i\)这个位置后变为\(0\)。
然后这个数组的总和就是所有的无法完成的情况。也就是答案的补集,直接用\(n*(n-1)/2\)减去即可。具体过程可以使用在前缀和上二分完成转移。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
ll a[200010],n,k,l,r;
ll Sum[200010],f[200010];
ll flag=0;
inline ll getSum(ll x,ll y) ;
int main()
{
ll T=read();
while(T--)
{
n=read(),k=read();
for(ll i=1;i<=n;i++)
{
a[i]=read();
Sum[i]=Sum[i-1]+a[i];
f[i]=0;
}
f[n+1]=0;ll Ans=0,ans;
for(ll i=1;i<=n;i++)
{
l=i,r=n;
while(l<=r)
{
ll mid=l+r>>1;
ll Get=getSum(i,mid);
if(Get>k)
{
r=mid-1;
}
else
{
l=mid+1;
}
}
Ans+=f[i];
f[l+1]+=f[i]+1;
}
Ans+=f[n+1];
ans=n*(n+1LL)/2-Ans;
cout<<ans<<endl;
}
return 0;
}
inline ll getSum(ll x,ll y)
{
if(x>y)return 0;
return Sum[y]-Sum[x-1];
}
D,赛时开始没做出来了。
首先是一个很简单也很重要的贪心,\(b-a\)是\(x\)的倍数,等价与\(b\)与\(a\)在\(\mod x\)的意义下相等。
这个在这题里面虽然作用不是特别关键,但是其实是非常重要的。
还有一个比较重要的事情,就是0是可以被任何数字整除的。这意味着,对于\(n\)个数字,一定存在两个数字在\(\mod n-1\)的意义下相等。
证明的话,我们就用\(1\)到\(n\)这\(n\)个数字举例,很明显,\(1\)和\(n\)在\(\mod n-1\)的意义下相等,如果我们无法把其中一个数字用另一个替换并且不产生其他在这个意义下相等的数字,就可以说明成立。任何我们发现,0可以被任何数字整除,也就是无法有相等的数字出现,否则他们一定可以被选取,那么剩下便没有位置了。也就是一定存在两个数字能够达成这个条件。
也就我们从最后一个\(n-1\)的条件开始使用,一定每次都能够连一条边,不存在无法连接的情况。
但是为什么不会出现环而导致生成树无法出现呢?
其实非常简单。连接完两个点之后,直接删掉里面的任意一个点,对于剩下的n-1个点,我们是取模n-2意义下的,依旧是一定能够出现答案的。所以,连接完之后,任意标记里面的一个点,然后连接的时候只需要找到没有被标记的点连接即可。
主要就是这个对于\(n\)个数字,一定存在两个数字在\(\mod n-1\)的意义下相等的结论的运用。
手玩其实就能看出来?
唉。变菜了。
不过学到了吧,也是。
这么简单的一个贪心,是我状态不好还是我对于生成树的理解确实不够呢?其实是对于那个前置的贪心的应用不够,一定存在,就是真的一定。不管怎么删去点都是一定存在的。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
int used[2001],vis[2001],n,a[2001];
int x[2001],y[2001],tot;
int main()
{
int T=read();
while(T--)
{
n=read();tot=0;
for(int i=1;i<=n;i++)
{
a[i]=read();used[i]=0;
}
for(int i=n-1;i>=1;i--)
{
// if(used[i])continue;
for(int j=0;j<i;j++)vis[j]=0;
for(int j=1;j<=n;j++)
{
if(used[j])continue;
int t=a[j]%i;
if(vis[t])
{
// cout<<vis[t]<<' '<<j<<endl;
x[++tot]=vis[t];
y[tot]=j;
used[j]=1;
break;
}
else
{
vis[t]=j;
}
}
}
// for(int i=1;i<=n;i++)
// {
// cout<<used[i]<<' ';
// }
// cout<<endl;
cout<<"Yes"<<endl;
for(int i=tot;i>=1;i--)
{
cout<<x[i]<<' '<<y[i]<<endl;
}
tot=0;
}
return 0;
}
E题,更是。。。炸裂,我觉得比D简单。
首先是cf经典前置贪心。对于单棵树,直接全取绝对最优。
然后考虑森林,可以发现,对于体积最大的那个树,怎么做也不可能让这个最大的数的最高位之上的二进制位变为1 。
所以最优秀的情况就是体积最大的直接全取,然后下面的尝试填补中间的1 。
然后就要用到那个砍树的操作了。我们完全没有必要砍子树。直接砍叶子,把这棵树砍成想要的大小。只动叶子,每次-1,而且每次都一定能够操作,所以我可以得到一个小于这树的任意大小的树。
那这个贪心,可以说是非常非常简单了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
ll n,a[1000001];
int main()
{
ll T=read();
while(T--)
{
n=read();ll ans=0;
for(ll i=1;i<=n;i++)
{
a[i]=read();
for(ll j=1;j<a[i];j++)read();
}
sort(a+1,a+1+n);
for(ll i=21;i>=0;i--)
{
ll Sum=0;
for(int j=1;j<=n;j++)
{
if((a[j]>>i)&1)
{
Sum++;
}
}
// cout<<Sum<<endl;
if(Sum>=2)
{
ans|=((1<<i+1)-1);
}
else
if(Sum==1)
{
ans|=(1<<i);
}
}
cout<<ans<<endl;
}
return 0;
}
破防了。。。本来上大分的。。。。