D - Ears
题目链接:D - Ears
大意:你在一个(0-L)的数轴上行走,从整数格出发,在整数格结束,可以在整数格转弯。每当你经过坐标为(i-0.5)的位置时((i)是整数),在(i)的位置放置一个石子。现在给出最后的石子序列,但这个序列有可能是不合法的,定义一次操作是将第(i)个位置上记录的石子(-1)或(+1),求最少的操作数使得给定序列成为一个合法序列
分析:
我们记录一次行走的起点为(S),终点为(T),在这次行走中到达的最左边的点为(L),最右边的点为(R)
那么这四个点将坐标轴分成了五个部分
(1):(0 ext~L-1),(A_i=0)
(2):(L ext~S-1),(A_i)为偶数(可以为0)
(3):(S ext ~T),(A_i)为奇数
(4):(T+1 ext~ R),(A_i)为偶数(可以为0)
(5):(R+1 ext~ INF),(A_i=0)
那么我们可以进行dp,记(dp[i][j])表示在第(i)号格子中时属于第(j)个部分
每一次从(i-1)号格子中的(leq j)的部分的最小值转移而来
注意在2,3,4部分中,如果(A_i=0)的话任然需要计算花费(为了到达下一个部分)
似乎可以滚动数组优化(但是懒qwq)
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxd=1000000007,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
int n;
ll a[200200],dp[200200][5];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
n=read();int i;
for (i=1;i<=n;i++) a[i]=read();
for (i=1;i<=n;i++)
{
ll mind=dp[i-1][0];
dp[i][0]=dp[i-1][0]+a[i];
mind=min(mind,dp[i-1][1]);
if (a[i]) dp[i][1]=mind+(a[i]%2);else dp[i][1]=mind+2;
mind=min(mind,dp[i-1][2]);
if (a[i]) dp[i][2]=mind+((a[i]+1)%2);else dp[i][2]=mind+1;
mind=min(mind,dp[i-1][3]);
if (a[i]) dp[i][3]=mind+(a[i]%2); else dp[i][3]=mind+2;
mind=min(mind,dp[i-1][4]);
dp[i][4]=mind+a[i];
}
ll ans=min(min(dp[n][2],dp[n][3]),dp[n][4]);
printf("%lld",ans);
return 0;
}
E - Odd Subrectangles
大意:给出一个(n)行(m)列的01矩阵,你可以选定一个行集合和列集合,使得那些行数和列数均在对应集合的矩阵中的位置上的数字之和是奇数,求方案数(感觉翻译的好奇怪啊QAQ,算了意思到了(逃)
分析:
将问题转化,“和为奇数”等价于“异或和为1”
将每一行都视作一个数((< 2^m)),我们考虑已经选择了任意的(i)行
首先如果说我们选定的这(i)行的异或之和为0的话,无论我们怎么选择列,选定格子的异或之和均为0
所以此时对应的方案数为0
那么如果这(i)行的异或和不为0的话,就说明是01交杂的
我们假设最后的异或和存在(a)个(1)和(b)个(0),那么首先就有(a+b=m)
同时要使所有选择的格子异或之和为奇数的话,那么我们一定会选择奇数个(1),而(0)的个数是任意的
选择(1)的方案数:(C^1_a+C^3_a+cdots+C^a_a(a ext%2=1)(或C_a^{a-1}当a ext%2=0)=2^{a-1})
(利用二项式定理和组合数的递推证明)
选择(0)的方案数:(2^b)
所以对于这(i)行而言合法的选择方案为(2^{a-1}*2^b=2^{a+b-1}=2^{m-1})
我们发现,无论(i)的值是多少以及我们如何选取这(i)行,最后选取的列的方案数是一定的
由于异或和不为(0)的方案数=总方案数-异或和为0的方案数
所以(ans=(2^n-行异或和为0的方案数)*2^{m-1})
行异或和的方案数可以用(bitset+)线性基完成,记最后被赋了值的线性基总数为(x)
则(ans=(2^n-2^{n-r})*2^{m-1})(对于剩下的(n-r)个未被插入线性基的数,我们可以选取它和可以表示它的线性基里面的数)
直接预处理2的幂次即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<bitset>
#include<map>
using namespace std;
const int maxd=998244353,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
int n,m,cnt=0;
ll bin[320];
bitset<320> a[320],p[320];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
n=read();m=read();
int i,j;
bin[0]=1;
for (i=1;i<=300;i++) bin[i]=(bin[i-1]*2)%maxd;
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++)
{
int tmp=read();
if (tmp) a[i][j]=1;
}
}
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++)
{
if (a[i][j])
{
if (p[j][j]) a[i]^=p[j];
else
{
p[j]=a[i];
cnt++;
break;
}
}
}
}
ll ans=(((bin[n]-bin[n-cnt])*bin[m-1])%maxd+maxd)%maxd;
printf("%lld",ans);
return 0;
}
F - Pass
题目链接:F - Pass
题目大意:(n)个人站成一排,开始时每个人手上有两个球(红色或蓝色),在每一个时刻第(i)个人如果手上有球就随机把一个球传给第(i-1)个人,第(1)个人传给小明,小明记录下球的颜色,问最后有可能记录下的球的颜色序列有多少种?
难得F比E还简单(虽然我没有做出来)
预处理(1-i)个人手上有几个红球几个蓝球
记(dp[i][j])表示已经记录了(i)个球的颜色,其中(j)个是红球
转移十分显然:(dp[i][j]=dp[i-1][j]+dp[i-1][j-1])
注意一下当前的红球数为(j),蓝球数为(i-j)
而在枚举(i)的时候红球和蓝球个数均有一个上界,不能超过这个上界
同时(j-1geq 0)即可
(ans=dp[2*n][红球个数])
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxd=998244353,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
int n,sumr[3030],sumb[3030],dp[4060][4060];
char s[3030];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
scanf("%s",s+1);
int i,j;n=strlen(s+1);
sumr[0]=0;sumb[0]=0;
for (i=1;i<=n;i++)
{
sumr[i]=sumr[i-1];sumb[i]=sumb[i-1];
if (s[i]=='0') sumr[i]+=2;
else if (s[i]=='1') {sumr[i]++;sumb[i]++;}
else if (s[i]=='2') sumb[i]+=2;
}
//for (i=1;i<=n;i++) cout << sumr[i] << " ";cout << endl;
//for (i=1;i<=n;i++) cout << sumb[i] << " ";cout << endl;
dp[0][0]=1;
for (i=1;i<=2*n;i++)
{
int nowr=sumr[min(i,n)],nowb=sumb[min(i,n)];
for (j=0;j<=i;j++)
{
if ((nowr>=j) && (nowb>=i-j))
{
dp[i][j]=dp[i-1][j];
if (j>0) dp[i][j]=(dp[i][j]+dp[i-1][j-1])%maxd;
}
}
}
printf("%lld",dp[2*n][sumr[n]]);
return 0;
}
每次做AtCoder的题都能感觉到自己的渺小