"蔚来杯"2022牛客暑期多校训练营7
C Constructive Problems Never Die
题意:
给你一个数组 A ,你需要构造一个排列 P ,使得P[i]≠A[i]
分析:
考虑构造不出来的情况 如果所有A[i]都相同一定不成立
先构造P[i]=i 如果P[i]=A[i] 就遍历一遍整个数组 找到一个P[j] 使得P[i] 和P[j] 交换之后分别都满足题意
复杂度:尽管是两层for循环 对于每个点i的最坏情况是第二层循环遍历完整个数组 这样原A数组数值都是i
第一层再遍历到其他点的时候 第二层循环只用遍历到第一个即可满足
#include <bits/stdc++.h>
using namespace std;
int a[100005],b[100005];
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=i;
}
bool flag=1;
for(int i=1;i<=n;i++){
if(a[i]==b[i]){
flag=0;
for(int j=1;j<=n;j++){
if(i!=j && a[i]!=b[j] && a[j]!=b[i]){
swap(b[i],b[j]);
flag=1;
}
}
if(flag==0) break;
}
}
if(flag){
cout<<"YES\n";
for(int i=1;i<=n;i++){
cout<<b[i]<<" ";
}cout<<endl;
}else cout<<"NO\n";
}
}
F Candies
分析:
发现其实两种方式删除先后对于最后答案是不会产生影响的 所以直接暴力删除就好了
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
vector<int>a;
int main()
{
cin>>n>>m;
for(int i=1,x;i<=n;i++)
{
cin>>x;
if(!a.empty()&&(a.back()==x||a.back()+x==m)) a.pop_back(),ans++;
else a.push_back(x);
}
int l=0,r=a.size()-1;
while(l<r&&(a[l]==a[r]||a[l]+a[r]==m)) ans++,l++,r--;
cout<<ans<<"\n";
return 0;
}
J Melborp Elcissalc
分析:
对于快速求区间和,采用前缀和算法
由于要求 k 的倍数,所以我们可以把前缀和 m o d k 容易想到,由取模而得到的前缀和是单一的,并且可以通过倒推得到
如何由前缀和快速判断是否优美
设sum为原数组的前缀和 区间[L,R]优美 当且仅sum[R]-sum[l-1]=0(mod k) 即sum[R]=sum[l-1](mod k)
前缀和为i的数有cnt个 通过组合数得到优美度为C(2,cnt)
那么因为数的范围为 [0,k-1],所以确定了模K的前缀和数组就可以唯一确定原数组
如何快速构造符合的数组
考虑DP解决 dp[i][j][k]表示用[0,i]的数 填充了j个位置 优美度为k 的数组数量
转移:
设加入的新数字的数量为 x ,剩余空位置有n-j个
dp[i+1][j+x][k+C(2,x)]+=dp[i][j][k]×C(x,n-j)
初始化 :
当i=0的时候需要特判不再是C(2,i) 而是C(2,i+1)
非常好的一道计数题目
#include<bits/stdc++.h>
using namespace std;
const int N=70;
const int mod=998244353;
int dp[N][N][N*N];
int C[N][N];
int inc[N],fac[N];
int n,t,k;
int ksm(int a,int b)
{
int ret=1;
while(b)
{
if(b&1)
ret=1ll*ret*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ret;
}
void init()
{
for(int i=0;i<N;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
}
int main()
{
init();
scanf("%d%d%d",&n,&k,&t);
for(int i=0;i<=n;i++)
dp[0][i][C[i+1][2]]=C[n][i];
for(int i=0;i<k-1;++i)
{
for(int j=0;j<=n;++j)
for(int l=0;l<=t;++l)
{
if(!dp[i][j][l])
continue;
for(int x=0;j+x<=n&&l+C[x][2]<=t;++x)
dp[i+1][j+x][l+C[x][2]]=(1ll*dp[i+1][j+x][l+C[x][2]]+dp[i][j][l]*C[n-j][x])%mod;
}
}
printf("%d",dp[k-1][n][t]);
}
K Great Party
显然,区间长度为1的时候,先手必胜。
如果区间长度为2。
两个人都不会执行合并操作的,因为这样对手就进入先手必胜态了。
如果两个数都相同的话,那么先手拿多少个石子,后手就在另外一堆拿相同数目的石子,这样就可以赖死先手。
所以区间长度为2,且两个数相同,先手必输。
并且如果两个数不同,先手直接拿最大的那一堆,使得剩下的两个数相同,后手面对先手必输的局面。
所以两个数不同,先手必胜。
如果区间长度为3,那么先手一定可以通过一次操作,带上合并,使得出现两个相同的数。
所以区间长度为3,先手必胜
如果区间长度为4,显然,二者都不可能在自己这一轮,拿完一堆石子。
不然对手就区间长度为3,获胜了。
所以大家拿到 [1,1,1,1]就不能再拿了。和Nim游戏对比 [0,0,0,0]不能拿到
所以区间长度为4就是执行 a[i]-1 的Nim游戏。
综上:
区间长度为奇数时,先手必胜。
区间长度为偶数时,执行减一的Nim游戏。
我们只考虑先手必输的局面,就是区间长度为偶数,且减一的异或值等于0.
那么我们利用前缀异或和。区间 [L,R] 异或等于0,即 pre[L-1]==pre[R] 。
因为要求区间长度为偶数,所以我们直接将 pre[i]根据 i 的奇偶分开。
然后就是跑个莫队统计答案了
注意:异或和的数组要开大一点 !!!!
不用C(2,n) 直接每次莫队的时候加减当前有多少个即可!!!
#include<bits/stdc++.h>
using namespace std;
inline int in(){ // 快读
int x;
scanf("%d",&x);
return x;
}
const int N=1e5+5,M=1<<20|5,B=300;
int n,Q;
int a[N],s[N];
struct qes{
int l,r,id;
}q[N];
inline bool cmp(qes a,qes b){
int x=a.l/B,y=b.l/B;
if(x==y)return a.r<b.r;
return x<y;
}
long long res,ans[N];
int c[2][M];
void ins(int p){
res += c[p&1][s[p]]++;
}
void del(int p){
res -= --c[p&1][s[p]];
}
int main(){ // NIM变式+莫队
n=in(); Q=in();
for(int i=1;i<=n;i++){
a[i]=in()-1; // 要-1,每堆留下一个石子
s[i]=s[i-1]^a[i]; // 异或前缀和
}
for(int i=1;i<=Q;i++){
q[i].l=in()-1;
q[i].r=in();
q[i].id=i;
}
sort(q+1,q+Q+1,cmp);
int L=1,R=0;
for(int i=1;i<=Q;i++){
int l=q[i].l,r=q[i].r;
while(R<r)ins(++R);
while(L>l)ins(--L);
while(R>r)del(R--);
while(L<l)del(L++);
int len=r-l;
ans[q[i].id]=(long long)len*(len+1)/2-res;
}
for(int i=1;i<=Q;i++)printf("%lld\n",ans[i]);
return 0;
}