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:
题中的式子可以转化成:
判断一些特殊情况,即可。
#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次幂) ,然后答案公式就是:
#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\) 前那么也算进去)
然后思考转移方程式:
- 如果当前这个数字就是 \(x\),那么\(dp[i][j]=dp[i−1][j]\)(一定是取这个数字,不然没贡献)
- 如果当前这个数字小于 \(x\),或者和x相同大但是位置比 \(x\) 前 \(dp[i][j]=dp[i−1][j]+dp[i−1][j−1]\)(前面是不取,后面是取,这里要注意j为0的情况)
- 如果当前这个数字大于 \(x\),或者和 \(x\) 相同大但是位置比 \(x\)靠后 \(dp[i][j]=dp[i−1][j]+dp[i−1][j]\) (前面这个是取,后面是不取)
- 如果当前这个数字是符号,那么我们再进行分类:
- 如果当前 \(j\) 为\(0\),那么 \(dp[i][0]=dp[i−1][1]+dp[i−1][0]+dp[i−1][j]\)(前两个是取,后面是不取)
- 如果当前 \(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\) 方程求出:
考虑计算满足 \(p[1]>q[1]\) 的合法长度为 \(i\) 的排列对数 \(dp[i]\)。
令 \(p[1]=k,q[1]=j\),则这一位产生的逆序对数, \(p\) 产生了 \(k-1\) 个, \(q\) 产生了 \(k-1\) 个。
然后推出满足条件的 \(q\) 排列剩下 \(i-1\) 位的逆序对数区间,结合 \(F\) 就可以算出来值了:
记录 \(f\) 的前缀和,优化掉最后的 \(\sum\) 求出所有 \(dp[i]\) 改成枚举 \(k-j\)。
总的方案数就是:
代码:
#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],f[i][j-1]\)进行转移方程展开,时间降到 \(n^3\):
其中,\(sum[i][j]=\sum_{k\leq j}f[i][k]\)。
\(dp\) 数组求法:
枚举 \(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;
}