前言
比赛时只做对A,实在是丢脸.
正题
A A A
题意:给你一个n*m的矩阵01矩阵,每次操作使得一个子矩阵的数全部置反,求最少操作使得有一条只经过0就从左上到右下的方案.(只能往右或往下走)
思路:暴力枚举是不易找到边界的,我们考虑转换.
如果把一个子矩阵置反,相当于走原来的图中的1.
那么显然,最少操作数等价于路径上最少有多少个1的块.
线性dp即可.
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cctype>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=110,size=1<<20;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
char s[N][N];
int f[N][N],n,m;
int main() {
qr(n); qr(m); memset(f,63,sizeof f);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
f[1][1]=(s[1][1]=='#');
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++)
f[i+1][j]=min(f[i+1][j],f[i][j]+(s[i][j]!=s[i+1][j])),
f[i][j+1]=min(f[i][j+1],f[i][j]+(s[i][j]!=s[i][j+1]));
}
pr2(f[n][m]+1>>1);
return 0;
}
B B B
比赛的时候觉得题意简单但是不会.
但是赛后题解还是讲的很好的.
首先,把输入的[1,3]变为[0,2].(以下均认为输入为[0,2])
考虑弱化版本,输入只有[0,1].
那么
∣
x
−
y
∣
=
x
xor
y
|x-y|=x \operatorname{xor }y
∣x−y∣=xxory
那么每个数
i
i
i对结果的贡献就是
n
n
n行杨辉三角的第
i
i
i个数,大小正好为
C
n
−
1
i
−
1
=
(
n
−
1
)
!
(
i
−
1
)
!
(
n
−
i
)
!
C_{n-1}^{i-1}=\dfrac{(n-1)!}{(i-1)!(n-i)!}
Cn−1i−1=(i−1)!(n−i)!(n−1)!.
答案显然为[0,1],所以我们只在意
C
n
−
1
i
−
1
=
(
n
−
1
)
!
(
i
−
1
)
!
(
n
−
i
)
!
C_{n-1}^{i-1}=\dfrac{(n-1)!}{(i-1)!(n-i)!}
Cn−1i−1=(i−1)!(n−i)!(n−1)!的奇偶性,具体处理方法为求每个阶乘有多少个2的因子.
现在开始推广.如果输入有1,那么答案不能为2.
为啥呢?因为只要有1,到倒数第2行(只有2个数)就一定有1,答案只能为[0,1].
所以得到以下求解思路:
- 若输入有1,判断答案的奇偶性则可得到答案.
- 输入无1(即只可能有0,2),那么把每个数/2,就是与上面相同的问题啦.
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
int n,cnt[N],sum[N],tmp=2,ans;
char a[N];
int main() {
scanf("%d",&n); n--;scanf("%s",a);
for(int i=0;i<=n;i++) {
a[i]-='1';
if(a[i]==1) tmp=1;
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+(cnt[i]=(i&1?0:cnt[i>>1]+1));
for(int i=0;i<=n;i++)
if(sum[n]==sum[i]+sum[n-i])
(ans+=a[i]/tmp)&=1;
printf("%d\n",ans*tmp); return 0;
}
C C C
求一个神奇的图的最大权独立集.
显然可以贪心先选择权值较大的(不知道如何理性证明 ).
upd:我得到了兔队的回复.
总结:对于这个
(
i
+
j
+
k
)
(i+j+k)
(i+j+k)分层图,层数大的一个点要比所有层数比它小的节点的权值都要多,所以当然要先取层数大的啦.(同一层的点必然不交),为了取得尽量多,我们间隔着捡,这明显对应着一个公平组合问题.
所以我们把边看成指向权值大的.
然后把每个图当做有向图游戏那样求出所有的点的sg函数.(不能动者输)
每个图看作一个子游戏的话,那么全局的sg函数就为仨的异或值.
引理:对于全局的有向图必败态而言,它们必然无边相连.
证明:根据sg函数的定义,必败态只能接必胜态. Q.E.D.
然后,根据题解神奇的定义:一张有向图sg函数的最大值为 m \sqrt m m.
证明:设 f ( i ) 表 示 m a x s g = i 时 的 最 小 边 数 f(i)表示maxsg=i时的最小边数 f(i)表示maxsg=i时的最小边数,则有 f ( 0 ) = 0 , f ( i ) = f ( i − 1 ) + i = ( i + 1 ) ∗ i 2 f(0)=0,f(i)=f(i-1)+i=\dfrac{(i+1)*i}{2} f(0)=0,f(i)=f(i−1)+i=2(i+1)∗i
然后我们解方程 f ( x ) = ( x + 1 ) ∗ x 2 = m f(x)=\dfrac{(x+1)*x}{2}=m f(x)=2(x+1)∗x=m,则 x ≈ m x\approx \sqrt m x≈m
所以,可以抄得如下代码:
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cctype>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10,size=1<<20,mod=998244353;
char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
struct edge{int y,next;}a[N];int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]};last[x]=len;}
void upd(int &x) {x+=x>>31&mod;}
ll power(ll a,ll b=mod-2) {
ll c=1;
for( ;b;b>>=1,a=a*a%mod)
if(b&1) c=c*a%mod;
return c;
}
int n,m,f[N],sum[3][N],sz[3],g[N],num,ans,fir,inv=power(fir=power(10,18));
int main() {
qr(n);
for(int graph=0;graph<3;graph++) {
if(len) memset(last+1,0,n<<2),len=0;
qr(m); for(int j=1,x,y;j<=m;j++) {
qr(x),qr(y);
if(x>y) swap(x,y);
ins(x,y);
}
int now=power(fir,n);
for(int i=n; i;i--) {
//求sg函数(f)
++num;
for(int k=last[i];k;k=a[k].next)
g[f[a[k].y]]=num;
f[i]=0;
while(g[f[i]]==num) f[i]++;
sz[graph]=max(sz[graph],f[i]);
upd(sum[graph][f[i]]+=now-mod);
now=(ll)now*inv%mod;
}
}
for(int i=0;i<=sz[0];i++)
for(int j=0,k;j<=sz[1];j++) {
k=i^j;
upd(ans+=(ll)sum[0][i]*sum[1][j]%mod*sum[2][k]%mod-mod);
}
pr2(ans);
return 0;
}
D D D
DP好题.思维难度高,代码量小.
简明题意:有n个含三个元素的队列(
A
[
1...
n
]
[
1..3
]
A[1...n][1..3]
A[1...n][1..3]),(这3n个数构成一个3n的排列),每次操作如下:
选出最小队头,输出并删除.求不同的输出( P )数.
可以发现:若
A
[
i
]
[
j
]
>
A
[
i
]
[
j
+
1
]
A[i][j]>A[i][j+1]
A[i][j]>A[i][j+1]的话,那么
A
[
i
]
[
j
]
,
A
[
i
]
[
j
+
1
]
为
P
中
的
相
邻
位
置
A[i][j],A[i][j+1]为P中的相邻位置
A[i][j],A[i][j+1]为P中的相邻位置.
所以我们可以把
A
A
A进行压缩.设压缩成
B
B
B.
B
(
b
l
o
c
k
,
存
块
信
息
)
B(block,存块信息)
B(block,存块信息)数组中存放的元素为
A
[
i
]
中
的
前
缀
最
大
值
的
去
重
结
果
A[i]中的前缀最大值的去重结果
A[i]中的前缀最大值的去重结果.
举个栗子,设
A
=
{
{
3
,
1
,
2
}
,
{
5
,
6
,
1
}
}
,
则
B
=
{
3
,
2
,
5
,
6
}
,
输
出
就
是
{
2
,
3
,
5
,
6
}
所
对
应
的
块
A=\{ \{3,1,2\},\{5,6,1\}\},则B=\{3,2,5,6\},输出就是\{2,3,5,6\}所对应的块
A={{3,1,2},{5,6,1}},则B={3,2,5,6},输出就是{2,3,5,6}所对应的块.
然后解题的重要转化就是
P
<
−
>
A
<
−
>
B
P<->A<->B
P<−>A<−>B.
映射关系:
定义域 | 映射 |
---|---|
A | B |
A | P |
所以,我们可以换一种思路:把判断
P
P
P是否成立转换为判断是否存在
B
B
B能与之对应.
B
B
B排序后有如下性质:
- 有序(废话)
- 每个B中元素对应A中至多3个元素,且B中元素对应长度为1的数量 ≥ \ge ≥B中元素对应长度为2的数量.
反过来,设B排序后的对应长度分别为
a
1
,
a
2
,
.
.
,
a
k
a_1,a_2,..,a_k
a1,a2,..,ak,则其对应的的
P
P
P的数量为
n
!
/
(
∏
j
=
1
k
(
∑
i
=
1
j
a
i
)
)
n!/(\prod_{j=1}^k (\sum_{i=1}^j a_i))
n!/(∏j=1k(∑i=1jai)).
因为排序后
B
[
1
]
是
a
1
个
元
素
中
最
小
的
,
成
立
的
概
率
为
1
/
a
1
B[1]是a_1个元素中最小的,成立的概率为1/a_1
B[1]是a1个元素中最小的,成立的概率为1/a1,
B
[
2
]
成
立
的
概
率
为
1
/
(
a
1
+
a
2
)
B[2]成立的概率为1/(a_1+a_2)
B[2]成立的概率为1/(a1+a2).
综上得证.
所以我们可以进行DP.
定义状态
f
[
i
]
[
j
]
f[i][j]
f[i][j],
i
=
∑
a
i
,
j
=
长
度
为
1
的
−
长
度
为
2
的
.
i=\sum a_i,j=长度为1的-长度为2的.
i=∑ai,j=长度为1的−长度为2的.
复杂度
O
(
n
2
)
O(n^2)
O(n2).
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cctype>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=12010,size=1<<20;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,mod,tot,f[N][N];ll inv[N],jc;//f[i][j]表示i个数,j表示1的个数-2的个数
void upd(int &x) {x+=x>>31&mod;}
int main() {
qr(n); qr(mod); tot=3*n+1;
f[0][tot]=1;
inv[1]=jc=1; for(int i=2;i<tot;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod,jc=jc*i%mod;
for(int i=1;i<tot;i++)
for(int j=-i+tot;j<=i+tot;j++) {
f[i][j]=f[i-1][j-1]*inv[i]%mod;
if(i>=2)upd(f[i][j]+=f[i-2][j+1]*inv[i]%mod-mod);
if(i>=3)upd(f[i][j]+=f[i-3][j ]*inv[i]%mod-mod);
}
ll ans=0;
for(int i=tot;i<=tot*2;i++) ans+=f[tot-1][i];
ans%=mod; pr2(ans*jc%mod);
return 0;
}