Solution Set - CSP2022

妈的,我考的跟狗屎一样,按道理稳定发挥差不多有 \(350\),不济也是 \(326\),可惜没有如果。

假期计划

首先先预处理出两点之间是否可达,这个可以使用 \(n\) 次 bfs 在最多 \(O(n(n+m))\) 的时间复杂度内求出。

预处理出对于每一个点 \(x\) 满足 \(x\to y\to 1\)\(y\) 的权值最大值,然后枚举 \(b,c\) meet-in-middle 一下,但是这样是错的,原因是没有考虑 \(a,b,c,d\) 互不相同这个限制。

考虑如何规避 \(a,b,c,d\) 存在相同的情况,不妨考虑存 \(k\) 个最大值,然后枚举 \(b,c\) 的时候暴力枚举 \(k^2\) 种情况,下文感性证明取 \(k=3\) 即可通过。

构造什么时候需要三个值,也就是说:\(b\) 的前 \(3\) 大分别为 \(c,x,y\)\(c\) 的前三大为 \(b,x,y\),此时取 \(x,y\) 满足条件而维护前两大不可行。

Code
const int N=2505;
int n,m,k,ma[N][10];
vi G[N];
bool f[N][N],vis[N];
ll h[N][N],val[N],ans;
int dis[N];
void bfs(int u) {
	FOR(i,1,n) dis[i]=0,vis[i]=0;
	queue<int> q;q.push(u);
	while(sz(q)) {
		int v=q.front();q.pop();
		f[u][v]=1;
		if(dis[v]==k+1) continue;
		for(int z:G[v]) if(!vis[z]) dis[z]=dis[v]+1,q.push(z),vis[z]=1;
 	}
}
int main() {
	scanf("%d %d %d",&n,&m,&k);
	FOR(i,2,n) scanf("%lld",&val[i]);
	FOR(i,1,m) {
		int x,y;
		scanf("%d %d",&x,&y);
		G[x].pb(y),G[y].pb(x);
	}
	FOR(i,1,n) bfs(i);
	FOR(i,2,n) FOR(j,2,n) if(i!=j&&f[i][j]&&f[j][1]) {
		int t=j;
		FOR(l,1,4) if(val[ma[i][l]]<val[t]) swap(ma[i][l],t);
	}
	FOR(i,2,n) FOR(j,2,n) if(i!=j&&f[i][j]) {
		ll tmp=val[i]+val[j];
		FOR(l1,1,4) FOR(l2,1,4) if(ma[i][l1]!=0&&ma[j][l2]!=0&&ma[i][l1]!=j&&ma[i][l1]!=ma[j][l2]&&ma[j][l2]!=i)
			ans=max(ans,tmp+val[ma[i][l1]]+val[ma[j][l2]]);
	}
	printf("%lld\n",ans);
}

策略游戏

若先手取正数,则后手一定取最小值,若先手取负数,则后手一定取最大值。

考虑先手的取法,无非是取最大,取最小,取最大的负数,取最小的正数,对这四种情况分类讨论即可。

求最大最小之类的可以使用线段树或者 ST 表搞定。

Code
const int N=1e5+5,B=17,inf=1e9+1;
int cz[N],cf[N],a[N],b[N];
struct node {
	int ma,mi,miz,maf;
	node operator + (const node &x) const {
		return {max(ma,x.ma),min(mi,x.mi),min(miz,x.miz),max(maf,x.maf)};
	}
} tr[N*4][2];
void build(int op,int p,int l,int r) {
	if(l==r) {
		int x=op?b[l]:a[l];
		tr[p][op]={x,x,x>=0?x:inf,x<=0?x:-inf};
		return;
	}
	int mid=(l+r)>>1;
	build(op,p*2,l,mid),build(op,p*2+1,mid+1,r);
	tr[p][op]=tr[p*2][op]+tr[p*2+1][op];
}
node query(int op,int p,int l,int r,int x,int y) { 
	if(x<=l&&r<=y) return tr[p][op];
	int mid=(l+r)>>1;
	if(x>mid) return query(op,p*2+1,mid+1,r,x,y);
	if(y<=mid) return query(op,p*2,l,mid,x,y);
	return query(op,p*2,l,mid,x,y)+query(op,p*2+1,mid+1,r,x,y);
}
int main() {
	int n,m,q;
	scanf("%d %d %d",&n,&m,&q);
	FOR(i,1,n) scanf("%d",&a[i]);
	FOR(i,1,m) {
		scanf("%d",&b[i]);
		cz[i]=cz[i-1]+(b[i]>=0),cf[i]=cf[i-1]+(b[i]<=0);
	}
	build(0,1,1,n),build(1,1,1,m);
	FOR(i,1,q) {
		int l,r,x,y,xx,yy;
		ll ans=-1ll*inf*inf;
		scanf("%d %d %d %d",&l,&r,&x,&y);
		int z=cz[y]-cz[x-1],f=cf[y]-cf[x-1];
		node a=query(0,1,1,n,l,r),b=query(1,1,1,m,x,y);
		xx=a.ma,yy=xx>=0?b.mi:b.ma,ans=max(ans,1ll*xx*yy);
		xx=a.mi,yy=xx>=0?b.mi:b.ma,ans=max(ans,1ll*xx*yy);
		if(a.miz!=inf) xx=a.miz,yy=xx>=0?b.mi:b.ma,ans=max(ans,1ll*xx*yy);
		if(a.maf!=-inf) xx=a.maf,yy=xx>=0?b.mi:b.ma,ans=max(ans,1ll*xx*yy);
		printf("%lld\n",ans);
	}
}

星战

首先你得先读懂题,题目问的等价于问图是不是基环树森林,换句话说就是问每个点出度是不是均为 \(1\)

有一个 naive 的思路是加和然后判断,这显然很假,考虑一点乱搞,我们为每个点随机一个权值 \(v\),这个点有多少个出度加多少个权值。

你发现这个东西可以做到单次操作 \(O(1)\),没了。

Code
const int N=5e5+5;
mt19937 rng(time(0));
ull val[N],now[N],sum[N],cur,ans;
int main() {
    int n=read(),m=read();
    FOR(i,1,n) val[i]=rng(),ans+=val[i];
    FOR(i,1,m) {
    	int x=read(),y=read();
    	cur+=val[x],now[y]+=val[x],sum[y]+=val[x];
    }
    int q=read();
    while(q--) {
    	int op=read(),x=read();
    	if(op==1) {
    		int y=read();
    		now[y]-=val[x],cur-=val[x];
    	}
    	if(op==2) cur-=now[x],now[x]=0;
    	if(op==3) {
    		int y=read();
    		now[y]+=val[x],cur+=val[x];
    	}
    	if(op==4) cur+=sum[x]-now[x],now[x]=sum[x];
    	puts(cur==ans?"YES":"NO");
    }
}

数据传输

考虑矩阵乘法刻画 DP。

先考虑序列上面怎么做,这个东西很好刻画,只要考虑从 \(f_{i-1},f_{i-2},f_{i-3}\) 转移即可,DP 上面记这几个就可以刻画出矩阵,也很好转移到树上。

然后你写写写,发现 \(k=3\) 挂了,冷静一下,发现当 \(k=3\) 的时候,当两个点距离为 \(4\) 的时候,可以从中间的某个点周转,考虑这种情况怎么处理,发现其实只需要让 \(f_{i-2}\) 可以转移到自身且代价为 \(i\) 这个节点儿子的最小值即可。

时间复杂度 \(O(3^3n\log n)\)

Code
const int N=2e5+5,B=18;
const ll inf=1e18;
int K;
struct mat {
    int n,m;
    ll a[3][3];
    mat operator * (const mat &x) const {
        mat ret={n,x.m,{{0}}};
        FOR(i,0,ret.n-1) FOR(j,0,ret.m-1) ret.a[i][j]=inf;
        FOR(i,0,ret.n-1) FOR(j,0,ret.m-1) FOR(k,0,m-1)
            chkmin(ret.a[i][j],a[i][k]+x.a[k][j]);
        return ret;
    } 
} I,val[N],up[N][19],dn[N][19];
vi G[N];
int a[N],ju[N][19],dep[N];
void dfs(int u,int fa) {
    dep[u]=dep[fa]+1,ju[u][0]=fa;
    if(K==3) for(int v:G[u]) chkmin(val[u].a[1][1],a[v]);
    up[u][0]=val[fa],dn[u][0]=val[u];
    FOR(i,1,B) ju[u][i]=ju[ju[u][i-1]][i-1],
        dn[u][i]=dn[ju[u][i-1]][i-1]*dn[u][i-1],
        up[u][i]=up[u][i-1]*up[ju[u][i-1]][i-1];
    for(int v:G[u]) if(v!=fa) dfs(v,u);
}
mat get(int u,int v) {
    mat a=I,b=I;
    if(dep[u]>dep[v]) ROF(i,B,0) if(dep[ju[u][i]]>=dep[v]) a=a*up[u][i],u=ju[u][i];
    if(dep[v]>dep[u]) ROF(i,B,0) if(dep[ju[v][i]]>=dep[u]) b=dn[v][i]*b,v=ju[v][i];
    if(u==v) return a*b;
    ROF(i,B,0) if(ju[u][i]!=ju[v][i]) a=a*up[u][i],b=dn[v][i]*b,u=ju[u][i],v=ju[v][i];
    return a*up[u][0]*dn[v][0]*b;
}
int main() {
    int n=read(),q=read();K=read();
    I={K,K,{{0,inf,inf},{inf,0,inf},{inf,inf,0}}};
    FOR(i,1,n) {
        a[i]=read();
        val[i]={K,K,{{inf,inf,inf},{inf,inf,inf},{inf,inf,inf}}};
        FOR(j,0,K-1) val[i].a[j][0]=a[i];
        FOR(j,1,K-1) val[i].a[j-1][j]=0;
    }
    FOR(i,1,n-1) {
        int x=read(),y=read();
        G[x].pb(y),G[y].pb(x);
    }
    dfs(1,0);
    FOR(i,1,q) {
        int x=read(),y=read();
        mat an={1,K,{{a[x],inf,inf}}};
        printf("%lld\n",(an*get(x,y)).a[0][0]);
    }
}
posted @ 2022-10-30 11:45  cnyz  阅读(356)  评论(2编辑  收藏  举报