本文迁移自洛谷原文

题目大意:

有一个 $4\times n$ 的 $01$ 矩阵。你每次可以选择一个 $k \times k (1 \le k \le 4)$ 大小的子矩阵,将其中的值都赋为 $0$,代价为 $a_k$。问你最小的代价使得整个矩阵的值都为 $0$。

题解:

萌萌题。

看到只有 $4$ 行就想到可以状压 $dp$。

令 $dp_{i,mask}$ 表示考虑到底 $i$ 列,当前第 $i-3$ 至 $i-1$ 列的状态为 $msk$。

我们考虑从前往后赋值。$dp_i$ 能够转移到 $dp_{i+1}$ 当且仅当第 $i-3$ 列已经全部赋值为 $0$了,因为到后面就再也不可能赋值它了。

但这个转移有点恶心,具体的可以参照代码里的注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using namespace std;
char c[5][1005];
int n,p,q,r,s;
bool a[5][1005];
int dp[1005][5555];
int tmp[5][5]; //tmp是当前考虑的第 i-3 列至第 i 列的矩阵的值
inline int get(){ //解压
int rt=0,cnt=0;
for(int i=1;i<=4;++i){
for(int j=2;j<=4;++j){
rt+=tmp[i][j]<<cnt;
++cnt;
}
}
return rt;
}
inline void rget(int msk){ //对这三列进行压缩
for(int i=1;i<=4;++i){
for(int j=1;j<=3;++j){
tmp[i][j]=msk&1;
msk>>=1;
}
}
}
inline void Min(int&x,int y){if(x>y)x=y;}
int tmpc[555][5][5],sta;//这个tmpc是在转移的时候存储tmp,应为我们要模拟赋值对tmp更改 前面还要开一维是应为它是递归,如果不开会覆盖上一层存储的东西
inline void cpy(){++sta;for(int i=1;i<=4;++i)for(int j=1;j<=4;++j)tmpc[sta][i][j]=tmp[i][j];}//存储
inline void rcpy(){for(int i=1;i<=4;++i)for(int j=1;j<=4;++j)tmp[i][j]=tmpc[sta][i][j];--sta;}//还原
inline void go(int i,int j,int msk,int cur=0){//考虑如何把最前面那一列全部赋为 0
if(j==5){//到头了
Min(dp[i+1][get()],dp[i][msk]+cur);
return;
}
if(j==1)Min(dp[i+1][0],dp[i][msk]+s);//覆盖 4*4 的
if(j<=2){ //3*3
cpy();
for(int e=0;e<3;++e)for(int f=0;f<3;++f)tmp[j+f][e+1]=0;
go(i,j+1,msk,cur+r);
rcpy();
}
if(j<=3){ //2*2
cpy();
for(int e=0;e<2;++e)for(int f=0;f<2;++f)tmp[j+f][e+1]=0;
go(i,j+1,msk,cur+q);
rcpy();
}
{//1*1
cpy();
tmp[j][1]=0;
go(i,j+1,msk,cur+p);
rcpy();
}
if(tmp[j][1]==0)go(i,j+1,msk,cur);//因为不用覆盖,可以考虑直接跳到下一行
}
inline void solve(){
cin>>n>>p>>q>>r>>s;
for(int i=1;i<=4;++i){
for(int j=1;j<=n;++j)cin>>c[i][j];
for(int j=n+1;j<=n+3;++j)c[i][j]='.';
}
n+=3;//我们从第4列开始考虑的,而且转移的时候是要求第 i-3 列全部清空,如果不 +3 的话第 n-2 列至第 n 列就会没有被考虑进去
for(int i=1;i<=4;++i){
for(int j=1;j<=n;++j){
if(c[i][j]=='.')a[i][j]=0;
else a[i][j]=1;
}
}
memset(dp,63,sizeof(dp));
int inf=dp[0][0];
for(int i=1;i<=4;++i)for(int j=1;j<=3;++j)tmp[i][j+1]=a[i][j];
dp[4][get()]=0;
for(int i=4;i<=n;++i){
for(int msk=0;msk<(1<<12);++msk){
if(dp[i][msk]>=inf)continue;
rget(msk);
for(int j=1;j<=4;++j)tmp[j][4]=a[j][i];
go(i,1,msk);
}
}
cout<<dp[n+1][0]<<'\n';
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T=1;
//cin>>T;
for(;T--;)solve();
return 0;
}

本文迁移自洛谷原文

题目大意:

给你一个长度为 $n$ 的字符串 $s$。

每一次操作,可以选择一个位置(不为最后一位),然后删除它和它后面一位,在原来的位置上填上他们的或。每次操作会使 $n-1$。

问你执行最多 $n-1$ 次操作后能得到多少不同的串。

题解:

根据套路,就可以把相同的数分成一段一段的。又因为是或,所以我们可以认为 $1$ 永远比 $0$ 强,就统计相邻两个 $1$ 之间 $0$ 的个数。(记第 $i$ 段为 $cnt_i$,有 $m$段)

考虑操作过程:

如果选择的是两个 $1$,那么段数会 $-1$,但由于反正间隔都是 $0$ 个 $0$了,我们就可以不用管了。(有问题,后面会提到)

反之,删除的肯定是一个 $0$,就当那一段的 $0$ 个数 $-1$。

然后我们就相当于:统计数组 $a$ 的个数,满足 $a_i \le cnt_i$ 了

吗?

不,这题不会这么简单,因为很显然会重复计算。

所以,我们考虑怎么去重:

前面推的时候,对于选择了两个 $1$ 的情况,很难自圆其说是不是?

我们对这一块重新考虑下。

令 $dp_i$ 表示考虑到 $cnt_i$ 时的方案数。

为了去重,当 $dp_i$ 由 $dp_k$ 转移过来的时候,需要满足对于所有的 $k+1 \le j \le i-1,cnt_j<cnt_i$,这样就限制了多次转移的情况,而且不会使答案遗漏。

处理 $k$ 的话,打个标记就好了。

最后用前缀和优化把复杂度从 $O(n^2)$ 降到 $O(n)$

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
string s;
const int mxn=1e6+6;
int cnt[mxn],m,n;
const ll md=1000000007;
ll sum[mxn],dp[mxn];
int pos[mxn];
inline void add(ll&x,ll y){
x+=y;
if(x>=md)x-=md;
if(x<0)x+=md;
if(x>=md)x-=md;
if(x<0)x+=md;
}
inline void solve(){
cin>>s;n=s.size();
s=s+"1";
for(int i=0,j=0;i<s.size();++i){
for(;j<s.size() and s[j]=='0';)++j;
// cerr<<"? "<<i<<' '<<j<<'\n';
cnt[++m]=j-i;
i=j;
j=i+1;
}
if(m==1){
cout<<n<<'\n';
return;
}
// for(int i=1;i<=m;++i)cerr<<cnt[i]<<' ';cerr<<'\n';
ll ans=(cnt[1]+1)*1ll*(cnt[m]+1)%md;
sum[1]=1,dp[1]=1;
for(int i=1;i<=n;++i)pos[i]=1;
for(int i=2;i<m;++i){
for(int j=0;j<=cnt[i];++j)add(dp[i],sum[i-1]-sum[pos[j]-1]);
for(int j=0;j<=cnt[i];++j)pos[j]=i;
add(sum[i],sum[i-1]+dp[i]);
}
cout<<(ans*1ll*sum[m-1])%md<<'\n';
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T=1;
//cin>>T;
for(;T--;)solve();
return 0;
}

鉴于本人能力有限,导致评论系统有些问题

所以如果您想在博客下评论,而登录github账号失败的话,请在 这篇文章 下的评论去登录,即可解决

我只是去摸鱼的。


总的来说机试要比去年难的多,大概CSP2019Day1的难度。


事实上这篇游记的目的大概就是介绍流程。

阅读全文 »

本文迁移自洛谷原文

Warning:可能比官方题解复杂的多,思维难度估计要上紫,但推出来了能有很好的训练效果

$\color{white}\text{然后你们知道为啥 div2 rank1 的 YuezhengLing 最后才过D了吧}$


令 $x$ 为隐藏的数组。

这个方法的大体思路就是,通过两次全数组的询问,得到两个数的位置($0$ 和最大的数),然后上传这两个位置。

我们考虑这么询问:

固定前两个数 $(1,2)$ 不动,询问 $(1,2,i)$,记得到的序列为 $a$。询问 $n-2$ 次。记最大的 $a_i$ 的下标 $i$ 为 $pos_1$。

然后固定 $(1,pos_1)$ 不动,询问 $(1,pos_1,i)$,记得到的序列为 $b$。询问 $n-2$ 次。记最大的 $b_i$ 的下标 $i$ 为 $pos_2$。

显然,$pos_1 \neq pos_2$,因为我们 $b$ 询问不会询问 $(1,pos_1,pos_1)$

然后我们来想,如何利用 $a$ 数组和 $b$ 数组,来推断出答案。

我赛时的注释:

1
2
3
4
5
6
7
8
9
//now if both x[1] and x[2] isn't 0 this is okay
//if x[2] is 0? what will happen?
//a[i] will be x[i]. How to ensure it?
//we can ask(2,pos1,pos2) to check
//if x[1] is 0: it is smaller than either a[pos1] or a[pos2] we can return [1,1]
//if x[2] is 0: this returns a[pos1 or pos2] then we can return [2,pos1 or pos2]
//otherwise we can return [pos1,pos2]
//oops,there still can be x[1] is max and pos1 is zero

我们要分类讨论(如何判断是哪一类待会儿讲):

  • $0$ 和最大的数都不在 $1$ 和 $2$ 两个位置上

显然,对于这种情况,我们求得的 $pos_1$ 和 $pos_2$ 就可以做为答案上传。因为,如果 $x_1$ 和 $x_2$ 都不是 $0$ 或最大值的话,第一遍求得的 $pos_1$ 肯定是 $0$ 或 最大值。(如果想不明白的话,画个数轴看看就知道了)

然后第二遍询问,因为询问的东西带上了 $pos_1$,故得到的 $pos_2$ 一定是另外一个极值。(因为这样的话 $max-min$ 才会最大)

  • $b_i$ 的值全相同

我们的 $pos_1$ 肯定是一个极值(排除 $x_1,x_2$ 均为极值的情况),然后就可以肯定 $(1,pos_1)$ 为两个极值,上传。

  • $a_i$ 的值全相同

有可能 $x_1$ 是极值,所以我们需要用一次询问来特判这种情况。

询问$(1,pos_1,pos_2)$,返回值记为 $tmp$。

如果说,$tmp$ 大于任意 $a_i$ 的话,我们可以直接返回 $(1,2)$,感性理解容易。

  • Otherwise

通过不断的尝试,我发现此时询问 $(2,pos_1,pos_2)$ 可以有很大的用处:

我们令这次询问的返回值为 $rt$。

  • ($rt < a_{pos_1}$ 且 $rt \le a_{pos_2}$)或($rt \le a_{pos_2}$ 且 $rt < a_{pos_1}$)

注意,一个是严格小于,另一个是小于等于。

这种情况对应了 $x_1$ 或 $x_2$ 为 $0$,因为:

$a_{pos_2}$ 查询的是 $(1,2,pos_2)$,$rt$ 查询的是 $(2,pos_1,pos_2)$

两者的区别就在于,一个是 $1$,一个是 $pos_1$。

然而,此时的 $rt$ 小于 $a_{pos_1}$,就说明,$x_1$ 会更往极端走一些。

然后到了这里,我们似乎发现了一个漏洞:$x_1$ 也许是最大值呀?

但这里我们之前已经排除这种情况了($a$ 数组全部相同的情况),故排除。

但上述讨论都是基于 $x_2$ 不为 $0$ 的情况。保险起见,我们上传 $(1,2)$ 做为可能的答案。

  • $rt=a_{pos_1}$

通过比较 $a_pos1$ 和 $rt$ 查询的内容可以发现,在这种情况下,$x_2$ 和 $x_{pos_1}$ 中必然有一个是 $0$。应为此时的 $pos_2$ 也应该是向着极值发展的,然而改变后并没有使 $rt$ 和 $a_{pos_1}$ 变得不同,故 $x_2$ 和 $x_{pos_1}$ 为两个极值,上传 $(2,pos_1)$

  • $rt=a_{pos_2}$

同理,上传 $(2,pos_2)$

  • Otherwise

平凡情况,上传 $(pos_1,pos_2)$


次数:

询问次数为 $2n-3$ 或 $2n-2$ 次,符合要求。


code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mp make_pair
#define reg register
const int mxn=1e3+3;
int n;
int a[mxn],b[mxn];
inline int ask(int x,int y,int z){
cout<<"? "<<x<<' '<<y<<' '<<z<<endl;
fflush(stdout);
int rt;cin>>rt;
return rt;
}
inline void print(int x,int y){
cout<<"! "<<x<<' '<<y<<endl;
fflush(stdout);
return;
}
inline void solve(){
cin>>n;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
int pos1=1;
int allsame=1;
int lst=-1;
for(int i=3;i<=n;++i){
a[i]=ask(1,2,i);
if(a[i]>a[pos1])pos1=i;
if(lst==-1)lst=a[i];
else{
if(a[i]!=lst){
lst=a[i];
allsame=0;
}
}
}
int allsame2=1,lst2=-1;
int pos2=1;
for(int i=2;i<=n;++i){
if(i==pos1)continue;
b[i]=ask(1,i,pos1);
if(b[i]>=b[pos2])pos2=i;
if(lst2==-1)lst2=b[i];
else if(lst2!=b[i])allsame2=0;
}
if(pos2==2)++pos2; //当n较小的时候可能会出现pos2=2的阴间情况,需手动更改
if(pos2==pos1)++pos2;

if(allsame==1){//this can all be x[1]-x[2]
int t=ask(1,pos1,pos2);
if(t<lst){
print(1,2);
return;
}
}
//now if both x[1] and x[2] isn't 0 this is okay
//if x[2] is 0? what will happen?
//a[i] will be x[i]. How to ensure it?
//we can ask(2,pos1,pos2) to check
//if x[1] is 0: it is smaller than either a[pos1] or a[pos2] we can return [1,1]
//if x[2] is 0: this returns a[pos1 or pos2] then we can return [2,pos1 or pos2]
//otherwise we can return [pos1,pos2]
//oops,there still can be x[1] is max and pos1 is zero
if(allsame2==1){
print(1,pos1);
return;
}
int t=ask(2,pos1,pos2);
if((t<a[pos1] and t<=a[pos2]) or (t<=a[pos1] and t<a[pos2])){
print(1,2);
return;
}
if(t==a[pos1]){
print(2,pos1);
return;
}
if(t==a[pos2]){
print(2,pos2);
return;
}

print(pos1,pos2);
return;
}
int main(){
int T=1;
cin>>T;
for(;T--;)solve();
return 0;
}

本文迁移自洛谷原文

此题我们可以考虑费用流

我们考虑建出一张 $n+2$ 个点的图(本文字符串下标从 $1$ 开始)

首先对于 $s$ 的一个子串 $s_{l\dots r}$,如果它可以和一个权值为 $w$ 的串匹配上,那么就建立一条从 $l$ 个点出发,连向第 $r+1$ 个点,流量为1,费用为 $w$ 的边;

然后,我们对于所有的 $1 \le i \le n+1$,建立一条从 $i$ 号点 出发,连向 $i+1$ 号点,流量为 $x$ (如题目所给,一个字符能被覆盖的最大次数),费用为0。

然后以 $1$ 号点为源点,$n+2$ 号点为汇点,跑最大费用最大流就可以了。

然后解释一下为什么这么建图是正确的:

建立从 $l$ 到 $r$,流量为 $1$ 的边很好理解,就是为了满足题目中所给的“每个串在每个位置只能匹配一次”的条件;

那么为什么建立从 $i$ 到 $i+1$,流量为 $x$ 的边就可以满足题目中要求“一个字符能被覆盖的最大次数为 $x$ ”的条件呢?

我们考虑一下,把那 $n+1$ 个点平铺在一个坐标轴上,这张流量网络大概长成什么样:(样例,边的方向都是从前往后,绿色为费用,蓝色为流量)

然后我们再尝试着把这张图拍扁。

如果对于一个点,它对应的字符出现了多于 $x$ 次,会出现什么情况?

它上面那些“高速公路”上的流量,最终一定会都流到“主路”上!

然而,如果他对应了多余 $x$ 次,那么它上方的“高速公路”上的流量就会大于 $x$,就超过了主路上相邻两点之间流量 $\le x$ 的限制。

那么为什么要建立这看似多余的第 $n+2$ 个节点呢?

其实很简单,万一有很多高速公路在 $n+1$ 号节点上才下来,这就不会受到主路的流量限制了啊!我们再连一条从 $n+1$ 号点通往 $n+2$ 号点的边,流量为 $x$,就正好处理了这种特殊情况。


Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include<bits/stdc++.h>
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int mxv=5005;
const int mxn=50005;
const int inf=1000000007;
struct edge{int to,cap,cost,rev;};
int V,m,s,t;
vector<edge>g[mxv],tmpg[mxv];
int h[mxv],dist[mxv],prevv[mxv],preve[mxv];
inline void add_edge(int from,int to,int cap,int cost){
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
inline int min_cost_flow(int f){
int res=0;
fill(h,h+V+1,0);
while(f>0){
priority_queue<pii,vector<pii>,greater<pii> >que;
fill(dist,dist+V+1,inf);
dist[s]=0;
que.push(mp(0,s));
for(;!que.empty();){
pii p=que.top();
que.pop();
int v=p.second;
if(dist[v]<p.first)continue;
for(int i=0;i<g[v].size();++i){
edge&e=g[v][i];
if(e.cap>0 and dist[e.to]>dist[v]+e.cost+h[v]-h[e.to]){
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(mp(dist[e.to],e.to));
}
}
}
if(dist[t]==inf) return -1;
for(int v=1;v<=V;++v) h[v]+=dist[v];
int d=f;
for(int v=t;v!=s;v=prevv[v]) d=min(d,g[prevv[v]][preve[v]].cap);
f-=d;
res+=d*h[t];
for(int v=t;v!=s;v=prevv[v]){
edge&e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return res;
}
int lev[mxn],iter[mxn];
inline void bfs(){
memset(lev,-1,sizeof(lev));
queue<int>q;
lev[s]=0;
q.push(s);
for(;q.size();){
int v=q.front();q.pop();
for(int i=0;i<g[v].size();++i){
edge&e=g[v][i];
if(e.cap>0 and lev[e.to]<0){
lev[e.to]=lev[v]+1;
q.push(e.to);
}
}
}
}
int dfs(int v,int t,int f){
if(v==t)return f;
for(int&i=iter[v];i<g[v].size();++i){
edge&e=g[v][i];
if(e.cap>0 and lev[v]<lev[e.to]){
int d=dfs(e.to,t,min(f,e.cap));
if(d>0){
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
inline int max_flow(){
int flow=0;
for(;;){
bfs();
if(lev[t]==-1)return flow;
memset(iter,0,sizeof(iter));
int f=0;
for(;;){
f=dfs(s,t,inf);
if(f==0) break;
flow+=f;
}
}
return flow;
}
inline void prep(){for(int i=1;i<=V;++i)tmpg[i]=g[i];}
inline void updg(){for(int i=1;i<=V;++i)g[i]=tmpg[i];}
int n,w,x;string ss;
inline void read(){
cin>>n>>ss>>w;
for(int i=1;i<=w;++i){
string tt;int v;
cin>>tt>>v;
for(int j=0;j<ss.size()-tt.size()+1;++j){
bool fit=1;
for(int k=0;k<tt.size();++k){
if(tt[k]!=ss[j+k]){
fit=0;
break;
}
}
if(fit==1)add_edge(j+1,j+tt.size()+1,1,-v);//这是最小费用最大流的板子,要处理最大费用流的话,就要将边权取负,跑最小费用流,再将答案取负就可以了
}
}
s=1,t=n+2;
V=n+2;
cin>>x;
for(int i=1;i<=n+1;++i)add_edge(i,i+1,x,0);
prep();
}
int main(){
read();
int MaxFlow=max_flow();
updg();
cout<<-min_cost_flow(MaxFlow)<<endl;
}

本文迁移自洛谷原文

晚上有点累就没打 Edu,感觉亏大本了。


首先,我们需要一个性质:

合法的图中不存在两个有着公共边的环

证明:

设环 $a$ 中不与环 $b$ 公用的部分的异或值为 $x$,环 $b$ 中不与环 $a$ 公用的部分的异或值为 $y$,环 $a$ 与 $b$ 公共部分的异或值为 $z$

那么,有:

  • $x \oplus z = 1$

  • $y \oplus z = 1$

  • $x \oplus y = 1$

(对应了环 $a$,环 $b$ 和新构成的环 $c$,其中 $\oplus$ 表示异或)

将这三个式子一起异或起来,就可以得到 $0 = 1$,显然不成立,故不会有两个有着公共边的环。

所以说,每次询问,就相当于询问,加上这条边后,会不会生成一个新环,且这个环与已经存在的环有着公共边。

还有一个性质:

如果我们把所有边离线下来,按照顺序加边,如果当前这条边不会生成一个环,那么它一定是可以加入的。

证明:

因为只有加上这条边后,会生成一个新环,且这个环与已经存在的环有着公共边,才会不能加,故在不生成环的情况下,是可以随便加边的。(也可以感性理解下)

所以我们就可以先按照第二个性质建出一棵树来,然后对于非树边一个一个判断。

于是,我们对于每一条可以加入的非树边,就对 $u_i$ 到 $v_i$ 的简单路径上的所有边权加一。在询问的时候,看 $u_i$ 到 $v_i$ 的简单路径上所有边的边权和是否为 0 即可。

看到是对树上的路径操作就很容易的想到了树剖。

至于题目要求是对边权进行加,我们可以把边权转换到点上:点 $i$ 的权值为连接 $i$ 与 $i$ 父亲的边的权值。

然后就可以码码码了。

我想练练手,所以代码里判断异或和就单独写了个倍增,没有再在树剖里搞,导致巨大多常数,1497ms

Code: 5130 Byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
const int mxn=5e5+5;
int n,q,root;
int val[mxn];
int bg[mxn],ed[mxn],top[mxn],sz[mxn],dep[mxn],dfsc;
int pa[mxn],ord[mxn],P[mxn][22],xo[mxn][22];
vector<int>g[mxn];
int par_val[mxn];
inline int dfs1(int x,int par=0,int deep=1){ //树剖基操
dep[x]=deep;
sz[x]=1;pa[x]=par;
P[x][0]=par;
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par)continue;
sz[x]+=dfs1(y,x,deep+1);
}
return sz[x];
}
inline void dfs(int x,int tpf=root,int par=0){
bg[x]=++dfsc;
top[x]=tpf;
ord[dfsc]=x;
int mx=-1,pos;
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par)continue;
if(sz[y]>mx){
mx=sz[y];
pos=y;
}
}
if(mx==-1){
ed[x]=dfsc;
return;
}
dfs(pos,tpf,x);
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par or y==pos)continue;
dfs(y,y,x);
}
ed[x]=dfsc;
}
struct segt{ //线段树维护树点权
int sum[mxn<<3];
int laz[mxn<<3];
int siz[mxn<<3];
inline void init(){
memset(siz,0,sizeof(siz));
memset(laz,0,sizeof(laz));
memset(sum,0,sizeof(sum));
}
inline void pushup(int x){sum[x]=sum[x<<1]+sum[x<<1|1];}
inline void pushdown(int x){
if(laz[x]){
sum[x]+=1ll*laz[x]*siz[x];
laz[x<<1]+=laz[x];
laz[x<<1|1]+=laz[x];
laz[x]=0;
}
}
inline void build(int x,int l,int r){
siz[x]=r-l+1;
if(l==r){
sum[x]=val[ord[l]];
return;
}
int md=l+r>>1;
build(x<<1,l,md);
build(x<<1|1,md+1,r);
pushup(x);
}
inline void add(int x,int l,int r,int a,int b,int d){
pushdown(x);
if(a<=l and r<=b){
laz[x]+=d;
pushdown(x);
return;
}
if(r<a or b<l)return;
int md=l+r>>1;
add(x<<1,l,md,a,b,d);
add(x<<1|1,md+1,r,a,b,d);
pushup(x);
}
inline int ask(int x,int l,int r,int a,int b){
pushdown(x);
if(a<=l and r<=b)return sum[x];
if(r<a or b<l)return 0;
int md=l+r>>1;
return ask(x<<1,l,md,a,b)+ask(x<<1|1,md+1,r,a,b);
}
}seg;
inline void updRange(int x,int y,int d){ //路径修改
for(;top[x]!=top[y];){
if(dep[top[x]]>dep[top[y]])swap(x,y);
seg.add(1,1,n,bg[top[y]],bg[y],d);
y=top[y];
y=pa[y];
}
if(dep[x]>dep[y])swap(x,y);
seg.add(1,1,n,bg[x],bg[y],d);
}
inline int qryRange(int x,int y){ //路径查询
int ans=0;
for(;top[x]!=top[y];){
if(dep[top[x]]>dep[top[y]])swap(x,y);
ans+=seg.ask(1,1,n,bg[top[y]],bg[y]);
y=top[y];
y=pa[y];
}
if(dep[x]>dep[y])swap(x,y);
ans+=seg.ask(1,1,n,bg[x],bg[y]);
return ans;
}
inline void addSub(int x,int d){ //闲着无聊把子树和也写了下,但没用
seg.add(1,1,n,bg[x],ed[x],d);
}
inline int qrySub(int x){return seg.ask(1,1,n,bg[x],ed[x]);}
struct dsu{ //并查集,用来离线后把所有树边提前加上
int fa[mxn];
inline void init(){for(int i=1;i<mxn;++i)fa[i]=i;}
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline int merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return 0;
fa[x]=y;
return 1;
}
}d;
int u[mxn],v[mxn],w[mxn];
int ans[mxn];
int roots[mxn];
inline void dfs_xor(int x,int P=0){ //写的倍增处理异或
xo[x][0]=par_val[x];
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==P)continue;
dfs_xor(y,x);
}
}
inline void init_xor(){
for(int j=1;j<=20;++j){
for(int i=1;i<=n;++i){
P[i][j]=P[P[i][j-1]][j-1];
xo[i][j]=xo[i][j-1]^xo[P[i][j-1]][j-1];
}
}
}
inline int qryXor(int x,int y){
int rt=0;
if(dep[x]>dep[y])swap(x,y);
for(int i=20;~i;--i)if(dep[P[y][i]]>=dep[x])rt^=xo[y][i],y=P[y][i];
for(int i=20;~i;--i)if(P[x][i]!=P[y][i])rt^=xo[x][i],rt^=xo[y][i],x=P[x][i],y=P[y][i];
if(x!=y){
rt^=xo[x][0],rt^=xo[y][0];
x=P[x][0],y=P[y][0];
}
return rt;
}
inline int lca(int x,int y){ //又用倍增写了个LCA
if(dep[x]>dep[y])swap(x,y);
for(int i=20;~i;--i)if(dep[P[y][i]]>=dep[x])y=P[y][i];
for(int i=20;~i;--i)if(P[x][i]!=P[y][i])x=P[x][i],y=P[y][i];
if(x!=y)x=P[x][0],y=P[y][0];
return x;
}
inline void solve(){
cin>>n>>q;
d.init();
for(int i=1;i<=q;++i){ //处理树边
cin>>u[i]>>v[i]>>w[i];
ans[i]=d.merge(u[i],v[i]);
if(ans[i])g[u[i]].push_back(v[i]),g[v[i]].push_back(u[i]);
}
for(int i=1;i<=n;++i)if(!top[i]){ //可能会有多个边
roots[i]=1;
dfs1(i);
dfs(i,i);
}
for(int i=1;i<=q;++i){
if(!ans[i])continue;
if(u[i]==pa[v[i]])par_val[v[i]]=w[i];
else par_val[u[i]]=w[i];
}
for(int i=1;i<=n;++i)
if(roots[i])
dfs_xor(i);
init_xor();
seg.build(1,1,n);
for(int i=1;i<=q;++i){ //边权转点权
if(ans[i]){
if(u[i]==pa[v[i]])val[v[i]]=w[i];
else val[u[i]]=w[i];
}
}
for(int i=1;i<=q;++i){
if(ans[i])continue;
int X=qryXor(u[i],v[i])^w[i];
if(X==0)continue;
int LCA=lca(u[i],v[i]);
// cerr<<u[i]<<' '<<v[i]<<' '<<w[i]<<'\n';
// for(int i=1;i<=n;++i)cerr<<qryRange(i,i)<<' ';cerr<<'\n';
int t=qryRange(u[i],v[i])-qryRange(LCA,LCA); //一定要减掉LCA处的点权 因为它对应的是LCA到LCA的父亲这条边的边权,不在路径上
// cerr<<"??? "<<qryRange(u[i],v[i])<<' '<<qryRange(u[i],u[i])<<' '<<qryRange(v[i],v[i])<<' '<<qryRange(LCA,LCA)<<'\n';
if(t!=0);
else{
ans[i]=1;
updRange(u[i],v[i],1); //同理
updRange(LCA,LCA,-1);
}
}
for(int i=1;i<=q;++i){
if(ans[i])cout<<"YES\n";
else cout<<"NO\n";
}
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;T=1;
// cin>>T;
for(;T--;)solve();
return 0;
}

本文迁移自洛谷原文

这道题的一血,纪念一下


观察一个数什么时候能对答案产生贡献。

从前往后数,如果在第 $i$ 个数之前,有 $j$ 个数被删除了,那么这个数现在的位置变成了 $i-j$,故当且仅当 $i-j = a_i$ 的时候会对答案产生贡献。

所以我们可以列出 dp 方程:

令 $dp_{i,j}$ 表示考虑到从前往后第 $i$ 个位置,在它之前已经删除了 $j$ 个数时,最大的答案。

可以得到如下的转移方程:

1
2
dp[i+1][j]=max(dp[i+1][j],dp[i][j]+(bool)(a[i]+j==i)); //保留这个数
dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]); //删除这个数

完事了~

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
const int mxn=2002;
int n,k,a[mxn];
int dp[mxn][mxn];
inline void solve(){
cin>>n>>k;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=0;i<=n+1;++i)for(int j=0;j<=n+1;++j)dp[i][j]=0;
for(int i=1;i<=n;++i){
for(int j=0;j<=n;++j){
dp[i+1][j]=max(dp[i+1][j],dp[i][j]+(bool)(a[i]+j==i));
dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]);
}
}
for(int j=0;j<=n;++j){
if(dp[n+1][j]>=k){
cout<<j<<'\n';
return;
}
}
cout<<"-1\n";
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;T=1;
cin>>T;
for(;T--;)solve();
}

本文迁移自洛谷原文

赛后写题解补教训。

场上数组开小本机 AC,但开了 O2 就会 RE,最终爆零。

洛谷上测由于数组开小导致访问不到、死递归 MLE,我还以为还是开大了。

小心,小心,再小心。


我们尝试建立一棵线段树。

线段树的每一个叶子节点是一行,每一个节点是一段行。

每一个节点存上两个东西:

  • 两个并查集,记录这个节点所对应的行区间的最上面一行和最下面一行的状态(后面会讲到)

  • 这个区间内部两种颜色的连通块的个数

对于单独的每一行,我们搞一个并查集:如果在这一行中,$i\dots j$ 的颜色相同,那么我们把 $i\dots j$ 都放到同一个集合中。用最小表示法,这个集合的代表就是最小的元素 $i$。

我们考虑合并两个行区间。

显然,新的行区间的上并查集就是左儿子的上并查集,新区间的下并查集就是右儿子的下并查集。

考虑左儿子的下并查集与右儿子的上并查集结合所带来的贡献。

首先令新节点的颜色 $c$ 的连通块数等于其两个儿子的该颜色的连通块数之和。

然后枚举,对于第 $i$ 列,如果 $gird_{md,i} = gird_{md+1,i}$,那么显然这两个在同一个连通块中,要将该颜色的数量-1。

然后写个标准的线段树就完事了~

总时间复杂度 O(能过) $O(n^2+mnlogn)$

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define int short
using namespace std;
const int mxn=808;
int gird[mxn][mxn],n,m,q;
struct dsu{
int fa[mxn<<1];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void uni(int x,int y){
x=find(x),y=find(y);
fa[x]=y;
}
inline void init(){for(int i=1;i<(mxn<<1);++i)fa[i]=i;}
}f;
int stand[mxn];
struct node{
dsu d;
int son[2],ans[2];
inline void init(){
ans[0]=0,ans[1]=0;d.init();
son[0]=0,son[1]=0;
}
}t[mxn<<1];
inline void ForceUpdate(int id,int x){
t[id].init();
t[id].ans[gird[x][1]]=1;
t[id].ans[1-gird[x][1]]=0;
int pre=1;
for(int i=1;i<=m;++i){
if(gird[x][i]!=gird[x][pre]){
++t[id].ans[gird[x][i]];
pre=i;
}
t[id].d.uni(i,pre),t[id].d.uni(i+n,pre);
}
}
inline void pushup(int id,int md,int l,int r){
f.init();
for(int i=0;i<2;++i)t[id].ans[i]=t[l].ans[i]+t[r].ans[i];
for(int i=1;i<=m*2;++i)f.fa[i]=t[l].d.fa[i],f.fa[i+m*2]=t[r].d.fa[i]+m*2;
for(int i=1;i<=m;++i){
int fx=f.find(i+m),fy=f.find(i+2*m);
if(gird[md][i]==gird[md+1][i] and fx!=fy){
--t[id].ans[gird[md][i]];
f.uni(fx,fy);
}
}
memset(stand,0,sizeof(stand)); // stand 数组的用处是最小表示
for(int i=m;i;--i){
f.fa[i]=f.find(i);
stand[f.fa[i]]=i;
}
for(int i=m*2;i>m;--i){
f.fa[i+m*2]=f.find(i+m*2);
stand[f.fa[i+m*2]]=i;
}
for(int i=1;i<=m;++i){
t[id].d.fa[i]=stand[f.fa[i]];
t[id].d.fa[i+m]=stand[f.fa[i+m*3]];
}
}
int cntnode=0,root;
inline void build(int&id,int l,int r){
id=++cntnode;
if(l==r){
ForceUpdate(id,l);
return;
}
int md=l+r>>1;
build(t[id].son[0],l,md);
build(t[id].son[1],md+1,r);
pushup(id,md,t[id].son[0],t[id].son[1]);
}
inline void upd(int id,int l,int r,int x){
if(l==r){
ForceUpdate(id,x);
return;
}
int md=l+r>>1;
if(x<=md)upd(t[id].son[0],l,md,x);
else upd(t[id].son[1],md+1,r,x);
pushup(id,md,t[id].son[0],t[id].son[1]);
}
inline void glhf(){
build(root,1,n);
for(;q--;){
int x,y;
cin>>x>>y;
gird[x][y]^=1;
upd(root,1,n,x);
cout<<t[root].ans[1]<<' '<<t[root].ans[0]<<'\n';
}
}
inline void solve(){
cin>>n;m=n;
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)cin>>gird[i][j];
cin>>q;
glhf();
}
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;T=1;
// cin>>T;
for(;T--;)solve();
return 0;
}
0%