"蔚来杯"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;
}


posted @ 2022-09-04 17:07  wzx_believer  阅读(32)  评论(0编辑  收藏  举报