2026 ZR暑假AB班联考
Day1
T1
注意到有一个很显然的必要条件,就是必须要有三个颜色。还有就是连续两个相邻节点肯定颜色不同,因为相邻两个节点肯定在一个三角形内。容易感觉这个也是充分的,考虑最极限的情况,就是一个颜色只有一个节点,那么这个节点可以向其他所有节点连边,显然也是合法的。所以说我们考虑减量构造,由于上述两个条件,我们可以每次找到一个合法的三个节点,然后割掉中间的点,但是我们有可能把唯一剩下的割掉,就是这种情况。
显然我们可以换一个方法割,这样还是可以剩下。
然后就作完了。可以使用链表维护割边。
Code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
namespace IO{
#define gc getchar
#define pc putchar
#define ps puts
inline int in(){
int x=0,f=1;
char c=gc();
for(;!isdigit(c);c=gc())if(c=='-')f=-1;
for(;isdigit(c);c=gc())x=x*10+(c-'0');
return x*f;
}
inline void out(int x){
if(x<0)pc('-'),x=-x;
if(x>=10)out(x/10);
pc(x%10+'0');
}
inline void outsp(int x){out(x),pc(' ');}
inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=1e6+5;
int n,a[N],c[3],pre[N],suf[N];
vector< pair<int,int> > ans;
bool C(int x,int y,int z){return a[x]+a[y]+a[z]==3&&a[x]!=a[y];}
int main(){
n=in();
for(int i=1;i<=n;i++)a[i]=in(),c[a[i]]++;
for(int i=1;i<=n;i++)pre[i]=(i+n-2)%n+1,suf[i]=i%n+1;
bool p=1;
for(int i=1;i<=n;i++)if(a[i]==a[suf[i]])p=0;
for(int i=0;i<3;i++)if(!c[i])p=0;
if(!p){out(-1);return 0;}
for(int i=1;i<=n;i++)if(C(pre[i],i,suf[i])){
int x=pre[i],y=i,z=suf[i];
for(int t=1;t<=n-3;t++){
if(C(pre[x],x,z))suf[x]=z,pre[z]=x,ans.pb({x,z}),y=x,x=pre[x];
else if(C(x,z,suf[z]))suf[x]=z,pre[z]=x,ans.pb({x,z}),y=z,z=suf[z];
else pre[y]=pre[x],suf[pre[x]]=y,ans.pb({pre[x],y}),x=y,y=z,z=suf[z];
}
break;
}
outln(ans.size());
for(auto [u,v]:ans)outsp(min(u,v)),outln(max(u,v));
return 0;
}
T3
乱搞
首先可以考虑每次从前往后排除一个最小值,然后选择尝试最后两个,因为最后两个肯定删得最少,这样期望会不断增高,但是如果删掉了最后两个,那么就往前面找,可以适当调整阈值就能过了。
Code
#include<bits/stdc++.h>
#include"pen.h"
using namespace std;
const int N=20;
int a[N],c[N];
bool p[N];
mt19937 rnd(random_device{}());
int find(int x){for(int i=x;i>=1;i--)if(!p[i])return i;return 0;}
pair<int,int> solve_one(int n){
for(int i=1;i<=n;i++)c[i]=p[i]=0;
for(int now=1;now<=7;now++){
for(int i=1;i<=n;i++)if(!p[i]){
while(c[i]<now){
if(!try_write(i)){p[i]=1;break;}
c[i]++;
}
if(p[i])break;
}
int u=find(n),v=find(u-1),x=0,y=0;
double z;
for(int i=now;i<n;i++)for(int j=i+1;j<n;j++){
if(i+j-c[u]-c[v]>=n)x++;
y++;
}
z=1.0*x/y;
if(now<=3&&z>0.7)break;
if(now>=4&&z>0.6)break;
}
int u=find(n),v=find(u-1);
return {u,v};
}
正解
考虑暴搜出状态数,每次往概率最大的地方走就行了。
得分
0+0+0=0,T1 没看到u<v输出格式错误挂完了,T3打错暴力导致错过了高分和正解
Day2
T1
正睿有原题,考虑每9个数肯定会经过其中一个,所以说我们可以找到路径中的一个点,把路径分成两半。发现这个东西可以使用猫树维护,每一层维护中间9个点到整个区间的最短路径,查询的时候只需要找到包含这两个的区间然后枚举中间点就行了。
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
namespace IO{
#define gc getchar
#define pc putchar
#define ps puts
inline int in(){
int x=0,f=1;
char c=gc();
for(;!isdigit(c);c=gc())if(c=='-')f=-1;
for(;isdigit(c);c=gc())x=x*10+(c-'0');
return x*f;
}
inline void out(int x){
if(x<0)pc('-'),x=-x;
if(x>=10)out(x/10);
pc(x%10+'0');
}
inline void outsp(int x){out(x),pc(' ');}
inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=2e5+55,L=35,inf=1e9;
int n=2e5+L,s,t,ans=1;
vector<int> g[N],h[N],st[N],ed[N];
queue<int>q;
void bfs(int x,int l,int r,vector<int> g[],vector<int> &d){
d.resize(r-l+1,inf),d[x-l]=0,q.push(x);
while(!q.empty()){
int u=q.front();q.pop();
for(int v:g[u])if(v>=l&&v<=r&&d[v-l]>=inf)d[v-l]=d[u-l]+1,q.push(v);
}
}
void init(int l,int r){
if(l>r)return;
int m=(l+r)>>1;
for(int i=max(l,m-4);i<=min(r,m+4);i++)bfs(i,max(0,l-L),min(n,r+L),g,st[i]),bfs(i,max(0,l-L),min(n,r+L),h,ed[i]);
init(l,m-5),init(m+5,r);
}
void solve(int l,int r){
if(l>r)return;
int m=(l+r)>>1;
if(max(m-4,min(s,t))<=min(m+4,max(s,t))){
int d=max(0,l-L);
for(int i=max(l,m-4);i<=min(r,m+4);i++)ans=min(ans,ed[i][s-d]+st[i][t-d]);
return;
}
if(s<m-4)solve(l,m-5);
else solve(m+5,r);
}
int main(){
freopen("digit.in","r",stdin);
freopen("digit.out","w",stdout);
for(int i=0;i<=n;i++)for(int j=i;j;j/=10){
int x=j%10;
g[i].pb(i-x),g[i].pb(i+x),h[i-x].pb(i),h[i+x].pb(i);
}
init(0,n);
int q=in();
while(q--){
s=in()^ans,t=in()^ans,ans=inf,solve(0,n);
if(ans>=inf)ans=-1;
outln(ans),ans++;
}
return 0;
}
T2
后缀问题,考虑建出SAM,然后式子就是形如
通过对v差分,化为
右边的东西有组合意义
把u的贡献拆到i上面
显然右边的东西可以整除分块,所以说我们需要一个支持 \(O(n)\) 次区间push/pop_back一些相同的数,\(O(n\sqrt n)\)次区间求和的数据结构,可以使用用树状数组区间加区间求和的trick改成分块,这样就可以做到 \(O(\sqrt n)\) 修改 \(O(1)\) 查询,总复杂度 \(O(\sqrt n)\)。
得分
100+20+5=125,没有挂分,但是T2还是硬实力不够,没办法还是要提高熟练度和解题技巧。
T3
Day3
正如原题赛
T1
华附校赛原题。考虑每次死掉一个人时,会让左右两边的点不能同时是好人,并且删除这个人。删除的操作我们可以用链表维护,但是左右两边不能同时是好人这个怎么维护呢?我们发现,好人的个数可以用(max,+)矩阵乘法来刻画,因为中间的人只对两边有影响,那我们一条边左右两个人不能同时取的条件就变成了这条边对应的矩阵 \(a_{1,1}\) 的权值为负无穷就行了,还有一个问题,就是你删除一个人的时候,这个人的左右两条边会合并,但是线段树上合并操作不好做,注意到我们只有合并没有分裂,而(max,+)矩阵是有单位矩阵的,所以我们把一个连通块最左边矩阵的当成代表元,值为所有矩阵的积,因为合并的时候内部的边已经考虑完了,其他边改成单位矩阵就行,最后环的要求就是首尾节点的状态要相同,总的时间复杂度\(O(n\log n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
namespace IO{
#define gc getchar
#define pc putchar
#define ps puts
inline int in(){
int x=0,f=1;
char c=gc();
for(;!isdigit(c);c=gc())if(c=='-')f=-1;
for(;isdigit(c);c=gc())x=x*10+(c-'0');
return x*f;
}
inline void out(int x){
if(x<0)pc('-'),x=-x;
if(x>=10)out(x/10);
pc(x%10+'0');
}
inline void outsp(int x){out(x),pc(' ');}
inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=2e5+5;
int t,n,L[N],R[N];
namespace SGT{
#define ls (k<<1)
#define rs (ls|1)
#define lm ((l+r)>>1)
#define rm (lm+1)
const int M=N<<2;
struct mat{
int a[2][2];
int *operator [](int x){return a[x];}
}a[M],mp[N],E,I;
mat operator *(mat x,mat y){
mat z{};
z[0][0]=max(x[0][0]+y[0][0],x[0][1]+y[1][0]);
z[0][1]=max(x[0][0]+y[0][1],x[0][1]+y[1][1]);
z[1][0]=max(x[1][0]+y[0][0],x[1][1]+y[1][0]);
z[1][1]=max(x[1][0]+y[0][1],x[1][1]+y[1][1]);
return z;
}
void pushup(int k){a[k]=a[ls]*a[rs];}
void build(int k,int l,int r){
if(l==r){a[k]=mp[l]=E,L[l]=R[l]=l;return;}
build(ls,l,lm),build(rs,rm,r);
pushup(k);
}
void upd(int k,int l,int r,int x){
if(l>x||r<x)return;
if(l==r){a[k]=mp[x];return;}
upd(ls,l,lm,x),upd(rs,rm,r,x);
pushup(k);
}
}
using namespace SGT;
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
E[0][1]=E[1][1]=1,I[0][1]=I[1][0]=-N;
in(),t=in();
while(t--){
n=in(),build(1,1,n);
for(int i=1;i<=n-3;i++){
int x=in(),u=L[(x+n-2)%n+1],v=R[x];
mp[u]=mp[u]*mp[x],mp[u][1][1]=-N,mp[x]=I,R[u]=v,L[v]=u;
upd(1,1,n,u),upd(1,1,n,x);
outsp(n-max(a[1][0][0],a[1][1][1]));
}
ps("");
}
return 0;
}
T2
吴浩岚太强了。首先考虑暴力dp,t是s的前缀的情况很好考虑,单独求就行了。对于其他情况,肯定跟s有一个公共前缀,然后t有一个(,s有一个),设 \(f_{a,b}\) 表示当在b这个位置与s不同,前b位有a个左括号,能生成多少个合法括号序列的方案数,那么我们枚举最终串的左括号的数量l,那么就相当于从(a,b),每次可以往右上或者往上走一格,且不能碰到y=2x+1,走到(l,2l)的方案数,那就是经典的反射容斥。由于每次都会往上走,所以考虑减掉x,然后就变成了从(a,b-a),每次可以往上或者往右走一个,且不能碰到y=x+1,最后走到(l,l)的方案数,那么就是\(\binom{2l-a-(a-b)}{l-a}-\binom{2l-a-(a-b)}{l-a-1}=\binom{2l-b}{l-a}-\binom{2l-b}{l-a-1}\),然后我正常推到这个式子就结束了,可以\(O(n^2)\)计算。我们发现,问题出在求\(f_{a,b}\)需要每次重新枚举长度,由于我们在遍历字符串时,每次a和b的变化最多只有1,这个sum肯定是可以重复利用的,所以我们考虑递推 \(f_{a,b}\),即通过 \(f_{a,b}\)求出\(f_{a,b+1}\)和\(f_{a+1,b+1}\)。首先可以通过拆组合数得到
然后我们只需要求出其中一个就可以求出另一个,现在只用到了组合数的性质,但是没有利用项数,然后我们会发现
所以我们可以求出\(f_{a+1,b+2}\)了,这样就可以通过\(f_{a,b}\)与\(f_{a,b+1}\)相互转移了,时间复杂度 \(O(n)\)。
Day4
T1
有一个人类智慧做法,首先我们考虑对于一个区间,有多少的边是有作用的,发现有很多边完全是没用的,我们大胆猜想只有三种边是可以有贡献的,第一种是左端点最大的,第二种是右端点最小的,第三种是长度最小的。证明就是考虑反证法,如果有另一个区间与当前区间的交比这三种都小,那么如果另一个区间被当前区间包含,显然交就是另一个区间的长度,肯定比最短区间长,如果没有包含关系,那么交就是一个右端点减去一个左端点,其中有一个端点是当前区间的端点,是定值,显然答案只跟另一个端点有关系。那么有一个点只会连3条边,直接kruskal做完了。
T2
考虑哪些灯会在第一轮熄灭。称 \(a_i\) 为灯的颜色,如果一个位置只被一种颜色覆盖,且覆盖该位置的灯全部在其右侧,那么在这之后的所有颜色大于等于当前颜色的灯全部熄灭,对于0种和多种就是右边所有灯熄灭或者不会熄灭。
Day5
T2
我们把黑/白边表示两边都是这个颜色的边。首先不可能有白边,白点的周围一定是黑点。然后,考虑对于每个黑边分出来的两个子树,我们发现边上的两个点在子树内一定是同胜负的,那么贡献就很好算了,考虑换根dp就行了,有一个问题就是有可能最后答案是模数的倍数,没有逆元,所以要用前后缀合并。
Day6
T1
由于过程比较难处理,考虑从结果来计算答案。首先最少交换次数肯定是前后两个序列顺序对个数的差,这个还是不太好做,考虑顺序对的定义就是每个1前面的0的数量,那么就转为了\(\sum_{i=1}^{c1}|lsta_i-lstb_i|\),然后我们发现,如果按k分块,在b序列上,同一块1的前面0的数量都相同,所以就变成\(\sum_{i=1}^{cb1}\sum_{j=1}^{k}|lsta_{g_{i,j}}-lstb_i|\),然后要求lstb一定是k的倍数且单调不降,我们发现假如去掉单调不降,那么就是经典的小奥问题,取中位数两边的k的倍数就行了,我们发现lsta也是单调不降的,所以这么做自然满足单调不降的条件。
T2
爆标题。
首先我们考虑分析从左到右把楼的左面依次填完,然后反向再操作一次,减去重复部分,下面可以证明这是对的。我们发现对于楼i的左面,我们发现这个会有3种方案,小于i-1的已经选了的点,i-1的右上角,i的左上角。我们发现这个方案是可以贪心确定的,如果第一种方案有点是合法的,那么就没必要建后面这两个了,第三种肯定不用建,因为对后面的没有作用,然后是第二种,很容易证明,如果第一种能覆盖,那么以后第二种肯定不优了,然后剩下两种可以通过i-1和i的高度区分。然后我们惊奇的发现,如果i-1高度大于i,那么对于反向的话,如果选i也没有贡献了,所以两个方向的选择是独立的。那么现在就有了\(O(n^2)\)的简单做法。我们发现复杂度的瓶颈只在第一种操作需要check以前的全部点,注意到check的式子很类似维护凸包,感性理解,发现只保留上凸包就是对的,然后就做完了。
Day7
T1
首先考虑y>=x怎么做,显然就不会有x操作了,y操作肯定能用就用。但是x>y就有点难办了,对于每一个数,肯定尽量多的用满x,最后剩下模x的余数。然后如果剩下的大于y,那么显然再做一次x把它消完是不亏的。那么就剩下最后一堆y了,首先肯定把x先做了,然后把y凑在一起做。我们发现这样不好维护。考虑一个trick,我们只需要每个后缀都满足需求就行了。必要性显然,充分性考虑每一个做完当前任务的时刻,从这个时刻开始的后缀就独立了,所以只需要满足每一个独立的需求就行了。那么就可以维护满足每一个后缀的最短合法时间,那么全局max就是答案。然后考虑答案的形式,设第i个数需要操作\(a_i\)次x,还剩下\(b_i\),那么对于一个后缀t,他的答案就是\(t-1+\sum_{s_i \ge t}a_i+\lfloor\frac{\sum_{s_i \ge t}b_i}{y}\rfloor\)你会发现,这个下取整不太好维护,那么考虑直接上下同乘y,然后最后求答案的时候在下取整就行了,那么修改操作其实就变成了后缀加/减然后求全局max,然后可能全局最大值需要用set维护。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
#define gc getchar
#define pc putchar
#define ps puts
inline int in(){
int x=0,f=1;
char c=gc();
for(;!isdigit(c);c=gc())if(c=='-')f=-1;
for(;isdigit(c);c=gc())x=x*10+(c-'0');
return x*f;
}
inline void out(int x){
if(x<0)pc('-'),x=-x;
if(x>=10)out(x/10);
pc(x%10+'0');
}
inline void outsp(int x){out(x),pc(' ');}
inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=4e5+5;
int n,m,q,X,Y,b[N];
set<int> S;
struct node{int p,c,s;}a[N],Q[N];
namespace SGT{
#define ls (k<<1)
#define rs (ls|1)
#define lm ((l+r)>>1)
#define rm (lm+1)
const int M=N<<2;
int mx[M],tag[M];
void adt(int k,int x){mx[k]+=x,tag[k]+=x;}
void pushup(int k){mx[k]=max(mx[ls],mx[rs]);}
void pushdown(int k){int &x=tag[k];adt(ls,x),adt(rs,x),x=0;}
void build(int k,int l,int r){
if(l==r){mx[k]=Y*(b[l]-1);return;}
build(ls,l,lm),build(rs,rm,r);
pushup(k);
}
void modify(int k,int l,int r,int t,int x){
if(l>t)return;
if(r<=t){adt(k,x);return;}
pushdown(k);
modify(ls,l,lm,t,x),modify(rs,rm,r,t,x);
pushup(k);
}
int query(int k,int l,int r,int t){
if(l>t)return 0;
if(r<=t)return mx[k];
pushdown(k);
return max(query(ls,l,lm,t),query(rs,rm,r,t));
}
}
using namespace SGT;
void upd(int p,int o){
int c=a[p].c,s=a[p].s,x=0,y=0;
if(X<=Y)y=c;
else{
x=c/X,y=c%X;
if(y>Y)x++,y=0;
}
modify(1,1,m,s,o*Y*x),modify(1,1,m,s,o*y);
if(~o)S.insert(s);
else S.erase(s);
}
void O(){outln((query(1,1,m,*S.rbegin())+Y-1)/Y);}
signed main(){
n=in(),q=in(),X=in(),Y=in();
for(int i=1;i<=n;i++)a[i]={i,in(),in()},b[++m]=a[i].s;
for(int i=1;i<=q;i++)Q[i]={in(),in(),in()},b[++m]=Q[i].s;
sort(b+1,b+m+1),m=unique(b+1,b+m+1)-b-1;
for(int i=1;i<=n;i++)a[i].s=lower_bound(b+1,b+m+1,a[i].s)-b;
for(int i=1;i<=q;i++)Q[i].s=lower_bound(b+1,b+m+1,Q[i].s)-b;
build(1,1,m);
for(int i=1;i<=n;i++)upd(i,1);
O();
for(int i=1;i<=q;i++){
int p=Q[i].p;
upd(p,-1),a[p].c=Q[i].c,a[p].s=Q[i].s,upd(p,1);
O();
}
return 0;
}
Day8
T1
首先考虑对于一个区间长度如何计算sg函数,这个题有cplus原题,通过打表和注意力可以知道,在前86个数是没有规律的,后面会进入循环节为34的循环,并且sg值最多为9。然后我们假如知道了一个长度的sg值,那么如何dp呢,考虑一个最简单的dp,\(f_{i,j}\)为考虑到前i个位置,当前整体的sg值为j的方案数。这个转移要枚举最后一个连续区间的长度,需要优化。我们发现,由于sg值到后面会循环,而我们的转移每次最后一个连续区间的长度会加一,所以说跟当前长度其实无关,只跟当前长度处于循环节哪一位有关。考虑将这个计入状态\(f_{i,j,k}\)中,这样转移就是\(O(1)\)的,然后状态数就是\(n \times 9 \times(34+86)\)的,可以通过。
T3
首先注意到区间肯定有单调性,被包含的区间的限制一定弱于包含它的区间。那么就可以求出对于每个r,最小的合法的l是多少就行了。然后r的答案肯定大于r-1的答案,所以说可以对r扫描线然后双指针。题目就转化为了已知[l,r-1]合法,如何判定[l,r]是否合法,首先它只要能选r就一定合法,因为[l,r-1]合法,所以一定是先连续删若干个l然后再删r,显然l删的越多越好,那我们只需要知道l最多能删到哪,然后再判断r是否能删就行了,这个判断是好做的,st表就行。l最多删到哪可以二分,那么问题就转化为了[l,m]在[l,r+1]里是否能被连续地删掉,发现这个问题等价于对于所有x属于[l,m],x在[x,r+1]是否能删。我们发现对于一个x,能删的r也是具有单调性的,所以说我们对于每个x维护最远的能删的r,然后取min就行了,也能用st表维护。
Day9
T1
首先有一个很显然的结论,一个人到一个出口会被烧到当且仅当这个点到出口的曼哈顿距离小区其他火源到出口的距离。必要性显然,充分性考虑反证法,假如有一个火源在终点前烧到,但是在终点时没烧到,这显然是不可能的。所以说问题就转为了两步,第一步是对每个终点,求出离它最近的火源的距离\(d_i\),第二步是对于每一个起点,查询是否存在一个终点的\(d_i\)小于当前这个点到终点的距离。第一步可以分类讨论火源和终点位置的相对关系拆绝对值,一共有四个种,可以四次扫描线,每次需要维护单点改和前/后缀求max,这个用树状数组是好做的。第二步看似很难做,但是有一个经典技巧,曼哈顿距离转切比雪夫距离,对于每个终点的合法起点范围就变成矩形了,那就是矩形覆盖求单点被覆盖次数,可以扫描线之后用区间加单点查的树状数组做。
T2
这题是内训的某一题的加强/弱化版,首先这一题可以弱化为每一次问一个集合是否是独立集,那么也能转化为问一个独立集到某一个点是否有边。如果用第一种转化,那么就等价于内训的题,次数无法通过。由于树是二分图,所以所有子图可以分成两个独立集。那么就很好办了,每次维护这两个独立集,然后从1到n加点,分别在这两个独立集内部二分就行了,每加一条边二分一次所以总次数是 \(n\log n+kn,k\ll \log n\)。
Day10
T1
考虑拆位,但是发现,有加法操作使得每一位不是独立的,所以说要同时考虑进位。我们不难发现,a,b,c三个数是轮换对称的,并且如果对于一个固定的x,y,z和进位,a,b,c也是固定的种类,0,0,0和1,1,1是一种方案,0,0,1和0,1,1是三种方案。所以最后的方案数一定是三的次幂。考虑dp,设\(f_{i,j}\)表示当前考虑到从高到低\(i\)位,后一位向第\(i\)位的进位为\(j\)有哪些方案数是可行的,这个答案可以用bitset记录。转移的话就是枚举上述4种方案来判断是否合法直接转移就行。时间复杂度\(O(\frac{n^2}{w})\),然后不合法的方案需要特判,具体就是枚举每一种填问号的方案,然后看看是否合法就行了。
Code
#include<bits/stdc++.h>
using namespace std;
namespace IO{
#define gc getchar
#define pc putchar
#define ps puts
inline int in(){
int x=0,f=1;
char c=gc();
for(;!isdigit(c);c=gc())if(c=='-')f=-1;
for(;isdigit(c);c=gc())x=x*10+(c-'0');
return x*f;
}
inline void out(int x){
if(x<0)pc('-'),x=-x;
if(x>=10)out(x/10);
pc(x%10+'0');
}
inline void outsp(int x){out(x),pc(' ');}
inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=3005,M=1e6+5;
int t,n,m;
bool p[N][3],ans[M];
bitset<N> f[N][3];
char X[N],Y[N],Z[N];
int main(){
t=in();
while(t--){
n=in(),m=in(),scanf("%s%s%s",X+1,Y+1,Z+1);
for(int i=0;i<=n;i++)for(int j=0;j<3;j++)f[i][j].reset();
f[0][0]=1;
for(int i=0;i<=n;i++)for(int j=0;j<3;j++)p[i][j]=0;
p[0][1]=p[0][2]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<3;j++){
for(int x=0;x<2;x++)for(int y=x;y<2;y++)for(int z=y;z<2;z++)
if((X[i]=='?'||(x&y&z)==X[i]-'0')&&(Y[i]=='?'||(x|y|z)==Y[i]-'0')&&(Z[i]=='?'||(x+y+z+j)%2==Z[i]-'0'))
f[i][j]|=f[i-1][(x+y+z+j)>>1]<<(x<y||y<z);
for(int a=0;a<2;a++)if(X[i]=='?'||a==X[i]-'0')
for(int b=0;b<2;b++)if(Y[i]=='?'||b==Y[i]-'0')
for(int c=0;c<2;c++)if(Z[i]=='?'||c==Z[i]-'0'){
bool f=1;
for(int x=0;x<2;x++)for(int y=x;y<2;y++)for(int z=y;z<2;z++)
if((x&y&z)==a&&(x|y|z)==b&&(x+y+z+j)%2==c&&!p[i-1][(x+y+z+j)>>1])
f=0;
p[i][j]|=f;
}
}
for(int i=0;i<=m;i++)ans[i]=0;
ans[0]=p[n][0];
for(int i=0,x=1%m;i<=n;i++,x=x*3%m)ans[x]|=f[n][0][i];
for(int i=0;i<m;i++)out(ans[i]);
ps("");
}
return 0;
}
T2
显然无法dp,这种类型的问题考虑网络流。源点向每个需求区间连边,边权为收益,每个展品向汇点连边,边权为修缮好的代价,然后所有需求区间向区间内的展品连正无穷的边,这样所有收益的和减去整个图的最小割显然是答案。
边数O(n^2)
显然这个直接建图边数是 \(O(n^2)\) 的,看似过不了,实则加一个区间内不能多次同一个展品的剪枝救过了。
边数O(nlogn)
这个是向一个区间连边,所以显然可以线段树优化建图,边数\(O(n \log n)\)轻松通过。
边数O(n)
非常精妙的做法,如果一个区间直接向前一个区间连边的话,另一边差分不了。可以按照优秀的拆分类似trick,按k撒点,然后一个长度为k的段一定包含一个前缀和后缀,这个是完全包含的。边数显然是\(O(n)\)的。
T3
最简单的一题,直接min-max容斥板子,然后有一个小问题,就是如果直接枚举\(2^{3n}\)显然会超时,但是我们发现同一种类型实际上是没有区分的,只需要枚举次数就行了,所以就变成\(4^n\),然后最后在答案里算上方案数的贡献就行了。
浙公网安备 33010602011771号