CF1542

CF1542

A:

直接判断有多少奇数,如果有奇数个奇数,就不行。

#include<bits/stdc++.h>
using namespace std;
int T,n,cnt;
int main()
{
    cin>>T;
    while(T--){
        cin>>n; cnt=0;
        for(int i=1,x;i<=n+n;i++) scanf("%d",&x),cnt+=(x&1);
        if(cnt!=n)puts("No");
        else puts("Yes");
    }
    // system("pause");
    return 0;
}

B:

题中的式子可以转化成:

\[n=a^x+y*b \]

判断一些特殊情况,即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int T,a,b,n;
signed main(){
    cin>>T;
    while(T--){
        scanf("%lld%lld%lld",&n,&a,&b);int k=n%b;
        if(a==1){
			if(n%b==1||n==1||b==1) cout<<"Yes\n";
			else cout<<"No\n";
			continue;
		}
		bool flag=0;
		for(int i=1;i<=n;i*=a) if(i%b==k) {flag=1;break;}
		if(flag) cout<<"Yes\n";
		else cout<<"No\n";
    }
    return 0;
}

C:

根据估算,每个数最小的不是因数的数最大为 \(40\)

我们要求出来 \([1,n]\) 的数有多少是 \(1*2,1*2*3,1*2*3*4...\) 的倍数。

我们预处理乘积(不用取余,同时需要因数为1次幂) ,然后答案公式就是:

\[\sum_{i=1}^{40} n/a[i] \]

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int a[100],T,n;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
signed main(){
	a[0]=a[1]=1;
	for(int i=2;i<=40;i++){
		int d=gcd(a[i-1],i);
		a[i]=a[i-1]*i/d;
	}
	cin>>T;
	while(T--){
		int ans=0;
		cin>>n;
		for(int i=0;i<=40;i++){//从0开始是假设初始不是都为1,而是为0。
			ans=(ans+n/a[i])%mod;
		}
		cout<<ans<<endl;
	}
    // system("pause");
	return 0;
}

D:

这道题目假如有长度为 \(n\),那么子序列就有\(2n\)个我们只要对于这个序列中的每一个数到底有多少个序列包含了它(我们在这里叫 \(x\))。这样就可以算出这个数对答案的贡献。

我们假设 \(dp[i][j]\) 的意思是在前 \(i\) 个字符串中选取的数中有 \(j\) 个数比 \(x\) 小(注意如果和 \(x\) 相同大,但是位置比 \(x\) 前那么也算进去)

然后思考转移方程式:

  1. 如果当前这个数字就是 \(x\),那么\(dp[i][j]=dp[i−1][j]\)(一定是取这个数字,不然没贡献)
  2. 如果当前这个数字小于 \(x\),或者和x相同大但是位置比 \(x\)\(dp[i][j]=dp[i−1][j]+dp[i−1][j−1]\)(前面是不取,后面是取,这里要注意j为0的情况)
  3. 如果当前这个数字大于 \(x\),或者和 \(x\) 相同大但是位置比 \(x\)靠后 \(dp[i][j]=dp[i−1][j]+dp[i−1][j]\) (前面这个是取,后面是不取)
  4. 如果当前这个数字是符号,那么我们再进行分类:
    1. 如果当前 \(j\)\(0\),那么 \(dp[i][0]=dp[i−1][1]+dp[i−1][0]+dp[i−1][j]\)(前两个是取,后面是不取)
    2. 如果当前 \(j\) 不是\(0\),那么 \(dp[i][j]=dp[i−1][j+1]+dp[i−1][j]\) (前面是取,后面是不取)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 505;
int dp[maxn][maxn];
int num[maxn],n;
void init(){
	memset(dp, 0, sizeof(dp));
	dp[0][0] = 1;
}
int main(){
	char ope;
	ll ans = 0;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf(" %c", &ope);
		if(ope == '-') num[i] = -1;
		else  scanf("%d", &num[i]);
	}
	for(int i = 1; i <= n; i++){
		if(num[i] == -1) continue;
		init();
		for(int z = 1; z <= n; z++){
			if(i == z)	for(int g = 0; g <= z; g++) dp[z][g] = dp[z-1][g];
			else if(num[z] == -1) {
				if(z < i)  dp[z][0] = (0ll + dp[z-1][0]*2 + dp[z-1][1])%mod;
				else  dp[z][0] = (0ll + dp[z-1][0]+dp[z-1][1])%mod;
				for(int g = 1; g <= z; g++) dp[z][g] = (0ll + dp[z-1][g+1] + dp[z-1][g])%mod;		
			}
			else if(num[z] < num[i] || (num[z] == num[i] && z < i)){
				dp[z][0] = dp[z-1][0];
				for(int g = 1; g <= z; g++) dp[z][g] = (0ll + dp[z-1][g] + dp[z-1][g-1])%mod;
			}
			else for(int g = 0; g <= z;g++)	dp[z][g] = (2ll*dp[z-1][g])%mod;
		}
		for(int g = 1; g <= n; g++) dp[n][g] = (dp[n][g] + dp[n][g-1])%mod;
		ans = (1ll*dp[n][n]*num[i] + ans)%mod;
	}
	printf("%lld", ans);
    // system("pause");
	return 0;
}

E1:

此处是借鉴了 Fizzmy的文章

首先用 \(f[i][j]\)表示满足在长度为 \(i\),有 \(j\)个逆序对的排列的个数,可以通过一个简单的 \(dp\) 方程求出:

\[f[i][j]=\sum_{k=0}^{min(i-1,j)} f[i-1][j-k] \]

考虑计算满足 \(p[1]>q[1]\) 的合法长度为 \(i\) 的排列对数 \(dp[i]\)

\(p[1]=k,q[1]=j\),则这一位产生的逆序对数, \(p\) 产生了 \(k-1\) 个, \(q\) 产生了 \(k-1\) 个。

然后推出满足条件的 \(q\) 排列剩下 \(i-1\) 位的逆序对数区间,结合 \(F\) 就可以算出来值了:

\[dp[i]=\sum_{j=1}^i \sum_{k=j+1}^i \sum_{q=k-j+1}^{i(i-1)/2}f[i-1][q]* \sum_{p=0}^{q-(k-j+1)} f[i-1][p] \]

记录 \(f\) 的前缀和,优化掉最后的 \(\sum\) 求出所有 \(dp[i]\) 改成枚举 \(k-j\)

总的方案数就是:

\[\sum_{i=1}^n A^{i-1}_n *dp[n-i+1] \]

代码:

#include<bits/stdc++.h>
using namespace std;
int n,mod,mi[55]; 
int f[55][3000],sum[55][3000];
int dp[55];
int main(){
	scanf("%d%d",&n,&mod);
	f[0][0]=1;sum[0][0]=1;
	for (int i=1;i<=n;i++)
		for (int j=0;j<=i*(i-1)/2;j++){
			for (int k=0;k<i&&j-k>=0;k++)
				f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
			if (j) sum[i][j]=(sum[i][j-1]+f[i][j])%mod;
			else sum[i][j]=f[i][j];
		}
	for (int i=1;i<=n;i++)
		for (int j=1;j<=i;j++)
			for (int k=j+1;k<=i;k++)
				for (int q=k-j+1;q<=i*(i-1)/2;q++)
					dp[i]=(dp[i]+1ll*f[i-1][q]*sum[i-1][q-(k-j+1)])%mod;
	mi[0]=1;
	for (int i=1;i<=n;i++) mi[i]=1ll*mi[i-1]*(n-i+1)%mod;
	int ans=dp[1];
	for (int i=1;i<=n;i++) ans=(ans+1ll*mi[n-i]*dp[i])%mod;
	printf("%d",ans);
}

E2:

考虑怎么优化:

算法是通过计算单个排列的种种情况,然后枚举导致字典序不同的位置的数的差值,从而间接求出排列对的情况数。

我们能不能跳过计算单个排列,直接计算排列对呢?

\(f[i][j]\) 表示长度为 \(i\) 的排列对 \((p,q)\) 中,满足 \(inv(q)−inv(p)=j\) 的对数有多少。

转移时我们考虑把新的数 \(i\) 分别放入两个排列,对每种提供的差值分别有多少种放入方式。

转移方程为:

\[f[i][j]=\sum_{k=-i+1}^{i-1} f[i-1][j-k]*(n-|k|) \]

我们 \(f[i][j],f[i][j-1]\)进行转移方程展开,时间降到 \(n^3\):

\[f[i][j]=f[i][j−1]−(sum[i−1][j−1]−sum[i−1][j−1−i]+(s[i−1][j−1+i]−s[i−1][j−1])) \]

其中,\(sum[i][j]=\sum_{k\leq j}f[i][k]\)

\(dp\) 数组求法:

\[d[i]=\sum_{j=1}^i\sum_{k=j+1}^i sum[i-1][-(k-j+1)] \]

枚举 \(k-j\)

答案与上题一样求法,注意负数情况,需要平移。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,mod,mi[550];
int T=130000;
int f[2][300000],sum[2][300000];
int dp[550];
int main()
{
	scanf("%d%d",&n,&mod);
	f[0][0+T]=1;sum[0][0+T]=1;
	for (int i=1;i<=10;i++) sum[0][i+T]=1;
	for (int i=1;i<=n;i++){
		int ii=i&1;
		for (int j=-i*(i-1)/2;j<=i*(i-1)/2;j++){
			f[ii][j+T]=(1ll*f[ii][j-1+T]-(1ll*sum[ii^1][j-1+T]-sum[ii^1][j-1-i+T])+(1ll*sum[ii^1][j-1+i+T]-sum[ii^1][j-1+T]))%mod;
			if (f[ii][j+T]<0) f[ii][j+T]+=mod;
		}
		for (int j=-i*(i-1)/2;j<=(i+1)*(i+2)/2;j++) sum[ii][j+T]=(sum[ii][j-1+T]+f[ii][j+T])%mod;
		for (int j=1;j<=i;j++) for (int k=j+1;k<=i;k++) dp[i]=(dp[i]+sum[ii^1][-(k-j+1)+T])%mod;
	}
	mi[0]=1;
	for (int i=1;i<=n;i++) mi[i]=1ll*mi[i-1]*(n-i+1)%mod;
	int ans=dp[1];
	for (int i=1;i<=n;i++) ans=(ans+1ll*mi[n-i]*dp[i])%mod;
	printf("%d\n",ans);
    // system("pause");
    return 0;
}


posted @ 2021-08-25 18:53  Evitagen  阅读(36)  评论(0编辑  收藏  举报