CNOI 浙江省系列比赛补题记录(from 2022)

只会放没打或者赛时没过的题。

代码不保证是现写的,所以码风会有一些不同。

CSP-S 2022

假期计划

直接枚举时间是炸的,根据数据范围我们考虑枚举其中两个。
尝试枚举 \(b,c\),并预处理合法且最优的 \(a,d\)
因为要求 \(a,b,c,d\) 不同,所以对每个点 \(u\) 预处理出不等于 \(u\) 且能够接在 1 与 \(u\) 之间的权值前三大的点即可。
最后只要枚举 \(b,c\)\(O(1)\) 枚举可能的 \(a,d\) 即可,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2505;
const int inf=1e9;
int n,m,k;
ll val[N];
vector<int>g[N];
int dis[N][N];
void bfs(int st){
	for(int i=1;i<=n;i++){
		dis[st][i]=inf;
	}
	dis[st][st]=0;
	queue<int>q;
	q.push(st);
	int u;
	while(!q.empty()){
		u=q.front();
		q.pop();
		for(auto v:g[u]){
			if(dis[st][v]>dis[st][u]+1){
				dis[st][v]=dis[st][u]+1;
				q.push(v);
			}
		}
	}
}
int pos[N][3];
int main(){
	scanf("%d %d %d",&n,&m,&k);
	k++;
	for(int i=2;i<=n;i++){
		scanf("%lld",&val[i]);
	}
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d %d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int u=1;u<=n;u++){
		bfs(u);
	}
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			if(j!=i&&dis[1][j]<=k&&dis[i][j]<=k){
				if(val[j]>val[pos[i][0]]){
					pos[i][2]=pos[i][1];
					pos[i][1]=pos[i][0];
					pos[i][0]=j;
				}
				else if(val[j]>val[pos[i][1]]){
					pos[i][2]=pos[i][1];
					pos[i][1]=j;
				}
				else if(val[j]>val[pos[i][2]]){
					pos[i][2]=j;
				}
			}
		}
	}
	ll ans=0;
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			if(i!=j&&dis[i][j]<=k){
				for(int x=0;x<=2;x++){
					for(int y=0;y<=2;y++){
						if(pos[i][x]&&pos[j][y]&&pos[i][x]!=j&&pos[i][x]!=pos[j][y]&&pos[j][y]!=i){
							ans=max(ans,val[pos[i][x]]+val[i]+val[j]+val[pos[j][y]]);
						}
					}
				}
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

星战

条件实际上就是基环树森林。

考虑 hash,对每个点的出边赋一个权值,对每个点的入边维护权值即可。

异或哈希只能判断从每个点出发是否是奇数条边,所以还要维护边数。和哈希就不用管了。

和哈希:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=5e5+5;
int n,m;
int vu[N];
int vusum,vsum[N];
int u,v;
int curs,cur[N];
int q,op;
int main()
{
	std::mt19937 rng(time(0));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		vu[i]=rng();
		vusum+=vu[i];
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&u,&v);
		vsum[v]+=vu[u];
	}
	for(int i=1;i<=n;i++)
	{
		cur[i]=vsum[i];
		curs+=cur[i];
	}
	scanf("%d",&q);
	while(q--)
	{
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d %d",&u,&v);
			curs-=cur[v];
			cur[v]-=vu[u];
			curs+=cur[v];
		}
		if(op==2)
		{
			scanf("%d",&u);
			curs-=cur[u];
			cur[u]=0;
			curs+=cur[u];
		}
		if(op==3)
		{
			scanf("%d %d",&u,&v);
			curs-=cur[v];
			cur[v]+=vu[u];
			curs+=cur[v];
		}
		if(op==4)
		{
			scanf("%d",&u);
			curs-=cur[u];
			cur[u]=vsum[u];
			curs+=cur[u];
		}
		if(curs==vusum)
		{
			printf("YES\n");
		}
		else
		{
			printf("NO\n");
		}
	}
	return 0;
}

异或哈希:

#include<bits/stdc++.h>
using namespace std;
namespace ax_by_c{
typedef unsigned long long ull;
const int N=5e5+5;
mt19937_64 Brnd(time(0));
int n,m,q,in[N],in_[N],_in;
ull h[N],xs[N],xs_[N],_xs,_xs_;
void main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)h[i]=Brnd(),_xs_^=h[i];
    for(int i=1,u,v;i<=m;i++){
        scanf("%d %d",&u,&v);
        xs_[v]^=h[u],in_[v]++;
    }
    for(int i=1;i<=n;i++)xs[i]=xs_[i],_xs^=xs[i],in[i]=in_[i],_in+=in[i];
    scanf("%d",&q);
    for(int i=1,op,u,v;i<=q;i++){
        scanf("%d",&op);
        if(op==1){
            scanf("%d %d",&u,&v);
            _xs^=xs[v];
            xs[v]^=h[u];
            _xs^=xs[v];
            _in-=in[v];
            in[v]--;
            _in+=in[v];
        }
        if(op==2){
            scanf("%d",&u);
            _xs^=xs[u];
            xs[u]=0;
            _xs^=xs[u];
            _in-=in[u];
            in[u]=0;
            _in+=in[u];
        }
        if(op==3){
            scanf("%d %d",&u,&v);
            _xs^=xs[v];
            xs[v]^=h[u];
            _xs^=xs[v];
            _in-=in[v];
            in[v]++;
            _in+=in[v];
        }
        if(op==4){
            scanf("%d",&u);
            _xs^=xs[u];
            xs[u]=xs_[u];
            _xs^=xs[u];
            _in-=in[u];
            in[u]=in_[u];
            _in+=in[u];
        }
        if(_xs==_xs_&&_in==n)puts("YES");
        else puts("NO");
    }
}
}
int main(){
    ax_by_c::main();
    return 0;
}

NOIP 2022

NOIP 2022 补题记录

ZJOI 2023

过河卒

注意到状态数是 \(O((nm)^3)\) 的,转移是 \(O(1)\) 的,考虑直接把状态转移图建出来。

然后类似拓扑排序,当一个点确定必胜或确定必败时加入队列,最后未确定的都没有必胜策略,但是都可以转移到环上,所以都是平局。

不难发现每个点会选择使其入队的点,因此可以简单计算步数。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
const int intinf=2e9;
const int N=12;
const int P=N*N*N*N*N*N;
const int ES=P*8;
const int dx[4]={-1,0,0,1};
const int dy[4]={0,-1,1,0};
int n,m;
char s[N][N];
int id[2][N][N][N][N][N][N],idx;
int F(int p,int xx,int yy,int x1,int y1,int x2,int y2){
	if(x1>x2)swap(x1,x2),swap(y1,y2);
	return id[p][xx][yy][x1][y1][x2][y2];
}
int get(){
	int xx_=0,yy_=0,x1_=0,y1_=0,x2_=0,y2_=0;
	rep(i,1,n)rep(j,1,m){
		if(s[i][j]=='X')xx_=i,yy_=j;
		if(s[i][j]=='O'){
			if(!x1_)x1_=i,y1_=j;
			else x2_=i,y2_=j;
		}
	}
	return F(0,xx_,yy_,x1_,y1_,x2_,y2_);
}
struct edge{
	int v,nxt;
}e[ES];
int hd[P],ec,in[P];
struct edge_{
	int v,nxt;
}e_[ES];
int hd_[P],ec_;
void add(int u,int v){
	e[++ec]={v,hd[u]};
	hd[u]=ec;
	in[v]++;
	e_[++ec_]={u,hd_[v]};
	hd_[v]=ec_;
}
int sg[P],dis[P],Q[P],ql,qr;
void slv(){
	scanf("%d %d",&n,&m);
	rep(i,1,n)scanf("%s",s[i]+1);
	rep(p,0,1)
	rep(xx,1,n)rep(yy,1,m)
	rep(x1,1,n)rep(y1,1,m)
	rep(x2,x1,n)rep(y2,1,m){
		if(x1==x2&&y1==y2)continue;
		if(s[xx][yy]=='#'||s[x1][y1]=='#'||s[x2][y2]=='#')continue;
		id[p][xx][yy][x1][y1][x2][y2]=++idx;
	}
	rep(p,0,1)
	rep(xx,1,n)rep(yy,1,m)
	rep(x1,1,n)rep(y1,1,m)
	rep(x2,x1,n)rep(y2,1,m){
		int u=F(p,xx,yy,x1,y1,x2,y2);
		if(!u)continue;
		if(!p){
			rep(k,0,3){
				int v=F(p^1,xx,yy,x1+dx[k],y1+dy[k],x2,y2);
				if(v)add(v,u);
				v=F(p^1,xx,yy,x1,y1,x2+dx[k],y2+dy[k]);
				if(v)add(v,u);
			}
		}
		else{
			rep(k,0,2){
				int v=F(p^1,xx+dx[k],yy+dy[k],x1,y1,x2,y2);
				if(v)add(v,u);
			}
		}
	}
	rep(p,0,1)
	rep(xx,1,n)rep(yy,1,m)
	rep(x1,1,n)rep(y1,1,m)
	rep(x2,x1,n)rep(y2,1,m){
		int u=F(p,xx,yy,x1,y1,x2,y2);
		if(!u)continue;
		if(xx==1){
			if(p==0)sg[u]=-1;
			else sg[u]=1;
			dis[u]=0;
			Q[++qr]=u;
			continue;
		}
		if((xx==x1&&yy==y1)||(xx==x2&&yy==y2)){
			sg[u]=-1;
			dis[u]=0;
			Q[++qr]=u;
			continue;
		}
		if(!in[u]){
			sg[u]=-1;
			dis[u]=0;
			Q[++qr]=u;
			continue;
		}
	}
	while(ql<=qr){
		int u=Q[ql++];
		if(sg[u]==-1){
			for(int i=hd[u];i;i=e[i].nxt){
				if(!sg[e[i].v]){
					sg[e[i].v]=1;
					dis[e[i].v]=dis[u]+1;
					Q[++qr]=e[i].v;
				}
			}
		}
		else{
			for(int i=hd[u];i;i=e[i].nxt){
				in[e[i].v]--;
				if(!in[e[i].v]&&!sg[e[i].v]){
					sg[e[i].v]=-1;
					dis[e[i].v]=dis[u]+1;
					Q[++qr]=e[i].v;
				}
			}
		}
	}
	int cur=get();
	if(!sg[cur])puts("Tie");
	else if(sg[cur]==1)printf("Red %d\n",dis[cur]);
	else printf("Black %d\n",dis[cur]);
	rep(i,1,idx)hd[i]=in[i]=sg[i]=hd_[i]=0;
	rep(p,0,1)
	rep(xx,1,n)rep(yy,1,m)
	rep(x1,1,n)rep(y1,1,m)
	rep(x2,x1,n)rep(y2,1,m){
		id[p][xx][yy][x1][y1][x2][y2]=0;
	}
	idx=ec=ec_=qr=0;
	ql=1;
}
void main(){
	int T=1;
	int csid=0;scanf("%d",&csid);
	scanf("%d",&T);
	while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

CSP-S 2023

消消乐

结论:设对前缀 \(s_1,\dots,s_i\) 做括号匹配后的栈为 \(stk_i\),那么 \(s_l,\dots,s_r\) 能被括号匹配当且仅当 \(stk_{l-1}=stk_r\)

于是用 trie 或哈希维护栈即可。

当时不知道结论所以不会做只拼了点分。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef pair<ull,ull> pir;
const int N=2e6+5;
int n;
char s[N];
ull p=131;
ull mm=1e9+9;
ull p1[N],p2[N];
char stk[N];
int top;
pir hsh;
map<pir,ull> mp;
long long ans;
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	p1[0]=p2[0]=1;
	for(int i=1;i<=n;i++)
	{
		p1[i]=p1[i-1]*p%mm;
		p2[i]=p2[i-1]*p;
	}
	mp[{0,0}]++;
	for(int i=1;i<=n;i++)
	{
		if(top&&stk[top]==s[i])
		{
			hsh.first-=p1[top]*stk[top];
			hsh.second-=p2[top]*stk[top];
			top--;
		}
		else
		{
			stk[++top]=s[i];
			hsh.first+=p1[top]*stk[top];
			hsh.second+=p2[top]*stk[top];
		}
		ans+=mp[hsh];
		mp[hsh]++;
	}
	printf("%lld",ans);
	return 0;
}

种树

先二分答案,相当于点 \(i\) 有一个最晚植树时间 \(lst_i\)(可以使用二分或数学求出),问能否满足。

设点 \(i\)\(t_i\) 被植树,题目中对操作的限制实际上就是 \(t_i\) 互不相同且 \(t_{fa_i}<t_i\)

于是从下往上扫一遍,\(lst_{fa_i}\leftarrow lst_i-1\),最后排个序判断是否合法即可。

当时想出来了但是不知道哪里写挂了,只过了前两个样例,最后拼出了 300 多行的代码还有一个暴力写挂了。

#include<bits/stdc++.h>
using namespace std;
char buf[1<<20],*p1,*p2;
inline char bgetc() {
	if(p1==p2) {
		p2=(p1=buf)+fread(buf,1,1<<20,stdin);
	}
	return (p1==p2)?EOF:*p1++;
}
typedef __int128 INT;
typedef long long ll;
char chch;
inline int BBread() {
	register int n=0;
	chch=bgetc();
	while(chch<'0'||'9'<chch) {
		chch=bgetc();
	}
	while('0'<=chch&&chch<='9') {
		n=n*10+chch-48;
		chch=bgetc();
	}
	return n;
}
inline ll Bread() {
	register ll f=1,n=0;
	chch=bgetc();
	while(chch<'0'||'9'<chch) {
		if(chch=='-') {
			f=-1;
		}
		chch=bgetc();
	}
	while('0'<=chch&&chch<='9') {
		n=n*10+chch-48;
		chch=bgetc();
	}
	return f*n;
}
const int N=1e5+5;
int n;
ll a[N],b[N],c[N];
int u,v;
struct edge{
	int v,nxt;
}e[N*2];
int hd[N],ec;
void add(int u,int v){
	e[++ec]={v,hd[u]};
	hd[u]=ec;
}
int ans=1e9;
ll be1[N];
inline void cal(int u) {
	register int l=1,r=1e9,mid,res=1e9+1;
	while(l<=r) {
		mid=l+((r-l)>>1);
		if(b[u]+mid*c[u]<=1) {
			res=mid;
			r=mid-1;
		} else {
			l=mid+1;
		}
	}
	be1[u]=res;
}
INT St,Ed;
inline INT calcc(int u,ll dy) {
	register INT res=0;
	if(be1[u]<=dy) {
		res+=dy-be1[u]+1;
		dy=be1[u]-1;
	}
	if(dy) {
		res+=(b[u]*2+c[u]*(dy+1))*(INT)(dy)/2;
	}
	return res;
}
inline INT calc(int u,ll l,ll r) {
	return calcc(u,r)-calcc(u,l-1);
}
ll las[N];
int fa[N];
int dfn[N],Time;
void dfs(int u) {
	dfn[++Time]=u;
	for(register int i=hd[u];i;i=e[i].nxt){
		if(e[i].v!=fa[u]){
			fa[e[i].v]=u;
			dfs(e[i].v);
		}
	}
}
inline bool check(ll fx) {
	for(register int i=1; i<=n; i++) {
		las[i]=-1;
		register int l=1,r=fx,mid;
		while(l<=r) {
			mid=l+((r-l)>>1);
			if(calc(i,mid,fx)>=a[i]) {
				las[i]=mid;
				l=mid+1;
			} else {
				r=mid-1;
			}
		}
		if(las[i]<1) {
			return 0;
		}
	}
	for(register int i=n; i>=2; i--) {
		las[fa[dfn[i]]]=((las[fa[dfn[i]]]<las[dfn[i]]-1)?(las[fa[dfn[i]]]):(las[dfn[i]]-1));
	}
	sort(las+1,las+1+n);
	for(register int i=1; i<=n; i++) {
		if(i>las[i]) {
			return 0;
		}
	}
	return 1;
}
int main() {
	n=BBread();
	for(register int i=1; i<=n; i++) {
		a[i]=Bread();
		b[i]=Bread();
		c[i]=Bread();
	}
	for(register int i=1; i<n; i++) {
		u=BBread();
		v=BBread();
		add(u,v);
		add(v,u);
	}
	dfs(1);
	for(register int i=1; i<=n; i++) {
		cal(i);
	}
	register int l=0,r=1e9,mid;
	while(l<=r) {
		mid=l+((r-l)>>1);
		if(check(mid)) {
			ans=mid;
			r=mid-1;
		} else {
			l=mid+1;
		}
	}
	printf("%d\n",ans);
	return 0;
}

NOIP 2023

天天爱打卡

这题我一开始看的时候状态设错了,常数很大,只能过 64。

首先转成 \((l-2,r)\),离散化。

那么就是有若干点,每次走 \((x+2,y)\) 区间。

考虑 DP,设 \(f_i\) 为最后一个走的点为 \(i\) 时获得的最大能量值。

用数据结构维护转移系数即可。

时间复杂度 \(O(m\log m)\)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
int fr(){
	int n=0;
	char c=getchar();
	while(c<'0'||'9'<c)c=getchar();
	while('0'<=c&&c<='9')n=n*10+c-48,c=getchar();
	return n;
}
typedef long long ll;
typedef pair<int,ll> pii;
const int M=2e5+5;
struct DS1{
	struct node{
		ll mx,tag;
	}tr[M*4];
	void pu(int u){
		tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
	}
	void pd(int u){
		tr[u<<1].mx+=tr[u].tag;
		tr[u<<1].tag+=tr[u].tag;
		tr[u<<1|1].mx+=tr[u].tag;
		tr[u<<1|1].tag+=tr[u].tag;
		tr[u].tag=0;
	}
	void bld(int u,int l,int r){
		tr[u].tag=0;
		if(l==r){
			tr[u].mx=0;
			return ;
		}
		int mid=l+((r-l)>>1);
		bld(u<<1,l,mid),bld(u<<1|1,mid+1,r);
		pu(u);
	}
	void add(int u,int l,int r,int ql,int qr,ll x){
		if(ql<=l&&r<=qr){
			tr[u].mx+=x;
			tr[u].tag+=x;
			return ;
		}
		pd(u);
		int mid=l+((r-l)>>1);
		if(ql<=mid)add(u<<1,l,mid,ql,qr,x);
		if(mid+1<=qr)add(u<<1|1,mid+1,r,ql,qr,x);
		pu(u);
	}
	ll Q(int u,int l,int r,int ql,int qr){
		if(ql<=l&&r<=qr)return tr[u].mx;
		pd(u);
		int mid=l+((r-l)>>1);
		ll res=0;
		if(ql<=mid)res=max(res,Q(u<<1,l,mid,ql,qr));
		if(mid+1<=qr)res=max(res,Q(u<<1|1,mid+1,r,ql,qr));
		return res;
	}
}tr;
int n,m,k,x[M],y[M],hsh[M],hc;
ll d,w[M],f[M];
vector<pii>fs[M];
void slv(){
	n=fr(),m=fr(),k=fr(),d=fr();
	rep(i,1,m)x[i]=fr(),y[i]=fr(),w[i]=fr(),x[i]-=y[i],y[i]+=x[i],x[i]--;
	rep(i,1,m)if(x[i]<-1||n<y[i])x[i]=0,y[i]=1,w[i]=0;
	hc=0;
	rep(i,1,m)hsh[++hc]=x[i],hsh[++hc]=y[i];
	sort(hsh+1,hsh+1+hc),hc=unique(hsh+1,hsh+1+hc)-hsh-1;
	rep(i,1,m)x[i]=lower_bound(hsh+1,hsh+1+hc,x[i])-hsh,y[i]=lower_bound(hsh+1,hsh+1+hc,y[i])-hsh;
	rep(i,1,hc)fs[i].clr();
	rep(i,1,m)fs[y[i]].pb({x[i],w[i]});
	tr.bld(1,1,hc);
	f[1]=0;
	tr.add(1,1,hc,1,1,f[1]+d*(hsh[1]+1));
	rep(i,2,hc){
		f[i]=f[i-1];
		for(auto it:fs[i])tr.add(1,1,hc,1,it.first,it.second);
		int l=1,r=i-1,mid,res=i;
		while(l<=r){
			mid=l+((r-l)>>1);
			if(hsh[i]-hsh[mid]-1<=k)res=mid,r=mid-1;
			else l=mid+1;
		}
		if(res<=i-1)f[i]=max(f[i],tr.Q(1,1,hc,res,i-1)-d*hsh[i]);
		tr.add(1,1,hc,i,i,f[i]+d*(hsh[i]+1));
	}
	printf("%lld\n",f[hc]);
}
void main(){
	int T=1;
	int csid=0;scanf("%d",&csid);
	scanf("%d",&T);
	while(T--)slv();
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

ZJOI 2024

季风

\(x,y\) 循环,拆一下绝对值。

  • \(\forall0\le i\le m-1,x'_i+y'_i\le k\)
  • \(\forall0\le i\le m-1,x'_i-y'_i\le k\)
  • \(\forall0\le i\le m-1,-x'_i+y'_i\le k\)
  • \(\forall0\le i\le m-1,-x'_i-y'_i\le k\)

发现只限制了 \(x',y'\) 的和,容易得到只需要和合法。

  • \(\sum_{i=0}^{m-1}(x_i+y_i+k)\ge x+y\)
  • \(\sum_{i=0}^{m-1}(x_i-y_i+k)\ge x-y\)
  • \(\sum_{i=0}^{m-1}(x_i-y_i-k)\le x-y\)
  • \(\sum_{i=0}^{m-1}(x_i+y_i-k)\le x+y\)

这几个都是类似的,下面考虑第一个。

\(S_m=\sum_{i=0}^{m-1}(x_i+y_i+k)\)

发现对于每个 \(m\bmod n\),将 \(S_m\) 列出来会得到一个等差数列,于是枚举 \(p=m\bmod n\)

\(f(i)=S_{ni+p}\),则 \(f\) 是一个一次函数,于是可以解不等式。

同理,解四次不等式即可得到上下界。

时间复杂度 \(O(n)\)

有个小插曲是 ceildiv 和 flrdiv 调了好久。

#include<bits/stdc++.h>
using namespace std;
namespace ax_by_c{
int fr(){
	int f=1,n=0;
	char c=getchar();
	while(c<'0'||'9'<c){
		if(c=='-')f=-1;
		c=getchar();
	}
	while('0'<=c&&c<='9'){
		n=n*10+c-48;
		c=getchar();
	}
	return f*n;
}
typedef long long ll;
const ll inf=1e18;
const int N=1e5+5;
int n;
ll k,X,Y,x[N],y[N],xs,ys;
ll flrdiv(ll x,ll y){
	if(x<0&&y>0||x>0&&y<0)return -1;
	if(x<0&&y<0){
		x=-x;
		y=-y;
	}
	return x/y;
}
ll ceildiv(ll x,ll y){
	if(y>0&&x<=-y||y<0&&x>=-y)return -1;
	return flrdiv(x,y)+bool(x%y);
}
void slv(){
	n=fr();
	k=fr();
	X=fr();
	Y=fr();
	xs=0;
	ys=0;
	for(int i=0;i<n;i++){
		x[i]=fr();
		y[i]=fr();
		xs+=x[i];
		ys+=y[i];
	}
	ll ans=-1,s1=0,s2=0,s3=0,s4=0,L,R;
	for(int p=0;p<n;p++){
		L=0;
		R=inf;
		if(p){
			s1+=x[p-1]+y[p-1]+k;
			s2+=x[p-1]-y[p-1]+k;
			s3+=x[p-1]-y[p-1]-k;
			s4+=x[p-1]+y[p-1]-k;
		}
		if(xs+ys+k*n==0&&0<X+Y-s1)continue;
		if(xs-ys+k*n==0&&0<X-Y-s2)continue;
		if(xs-ys-k*n==0&&0>X-Y-s3)continue;
		if(xs+ys-k*n==0&&0>X+Y-s4)continue;
		if(xs+ys+k*n!=0){
			if(xs+ys+k*n>0)L=max(L,ceildiv(X+Y-s1,xs+ys+k*n));
			else R=min(R,flrdiv(X+Y-s1,xs+ys+k*n));
		}
		if(xs-ys+k*n!=0){
			if(xs-ys+k*n>0)L=max(L,ceildiv(X-Y-s2,xs-ys+k*n));
			else R=min(R,flrdiv(X-Y-s2,xs-ys+k*n));
		}
		if(xs-ys-k*n!=0){
			if(xs-ys-k*n>0)R=min(R,flrdiv(X-Y-s3,xs-ys-k*n));
			else L=max(L,ceildiv(X-Y-s3,xs-ys-k*n));
		}
		if(xs+ys-k*n!=0){
			if(xs+ys-k*n>0)R=min(R,flrdiv(X+Y-s4,xs+ys-k*n));
			else L=max(L,ceildiv(X+Y-s4,xs+ys-k*n));
		}
		if(L<=R){
			if(ans==-1)ans=L*n+p;
			else ans=min(ans,L*n+p);
		}
	}
	printf("%lld\n",ans);
}
void main(){
	int T=fr();
	while(T--){
		slv();
	}
}
}
int main(){
	ax_by_c::main();
	return 0;
}

迷宫守卫

考虑通过求第一个数确定方向来求出答案。

考虑二分,考虑 DP,设 \(f_i\) 为使得 \(i\) 子树内访问的第一个数 \(\ge mid\) 所需的最小魔力,转移:\(f_u=f_{ls_u}+\min(w_u,f_{rs_u})\)

若第一个数在左子树内,那么至少需要 \(\min(w_u,f_{rs_u})\) 的魔力来保证会向左走,将剩下的魔力向左子树递归。

那么接下来就是选 \(w_u\) 还是 \(f_{rs_u}\) 的问题了。(这也是我最不懂的点,我如果来打这场比赛大概率会卡在这)为了让结果更优,一定是优先选 \(f_{rs,u}\),选不了才会选 \(w_u\)

若第一个数在右子树内就不用考虑 \(w_u\) 了,给左子树留 \(f_{ls_u}\) 的魔力后直接递归即可。

#include<bits/stdc++.h>
using namespace std;
namespace ax_by_c{
typedef long long ll;
const ll inf=1e18;
const int N=2e5+5;
int n,a[N],pos[N],L[N],R[N],Tim,ans[N],idx;
ll k,w[N],f[N];
void ddfs(int u){
    L[u]=++Tim;
    if(u<(1<<n))ddfs(u<<1),ddfs(u<<1|1);
    R[u]=Tim;
}
void dp(int u,int v){
    if((1<<n)<=u){
        if(a[u]>=v)f[u]=0;
        else f[u]=inf;
        return ;
    }
    dp(u<<1,v),dp(u<<1|1,v);
    f[u]=f[u<<1]+min(w[u],f[u<<1|1]);
}
ll dfs(int u,ll k){
    if((1<<n)<=u){
        ans[++idx]=a[u];
        return 0;
    }
    int l=1,r=(1<<n),mid,res=(1<<n);
    while(l<=r){
        mid=l+((r-l)>>1);
        dp(u,mid);
        if(f[u]<=k)res=mid,l=mid+1;
        else r=mid-1;
    }
    dp(u,res);
    ll ss=0;
    if(L[u<<1]<=L[pos[res]]&&L[pos[res]]<=R[u<<1]){
        ll ff=f[u<<1|1],tmp=dfs(u<<1,k-min(w[u],ff));
        k-=tmp,ss+=tmp;
        if(k>=ff){
            ss+=dfs(u<<1|1,k);
        }
        else{
            ss+=w[u]+dfs(u<<1|1,k-w[u]);
        }
    }
    else{
        ll tmp=dfs(u<<1|1,k-f[u<<1]);
        ss+=tmp+dfs(u<<1,k-tmp);
    }
    return ss;
}
void slv(){
    scanf("%d %lld",&n,&k);
    for(int i=1;i<(1<<n);i++)scanf("%lld",&w[i]);
    for(int i=(1<<n);i<=(1<<(n+1))-1;i++)scanf("%d",&a[i]),pos[a[i]]=i;
    Tim=0,ddfs(1);
    idx=0,dfs(1,k);
    for(int i=1;i<=idx;i++)printf("%d ",ans[i]);putchar('\n');
}
void main(){
    int T;
    scanf("%d",&T);
    while(T--)slv();
}
}
int main(){
    ax_by_c::main();
    return 0;
}

重塑时光(待写)

APIO 2024

魔术表演

5000 个点也太多了吧!!!

直接令 \(i+1\)\(X\bmod i+1\) 连边,因为 \(X\bmod i<i\) 所以打乱顺序没关系。

那么我们就得到了一堆同余方程,每次直接暴力合并即可,时间复杂度 \(O(n^2)\)

注意 lcm 不能超过数据类型上限。

Alice.cpp

#include<bits/stdc++.h>
long long setN(int n); 
std::vector<std::pair<int, int>> Alice(){
	long long n=setN(5000);
	std::vector<std::pair<int,int> >res;
	for(int i=1;i<=4999;i++)res.push_back({i+1,n%i+1});
	return res;
}
//long long setN(int n){
//	long long x;
//	std::cin>>x;
//	return x;
//}
//int main(){
//	freopen("C.txt","w",stdout);
//	auto res=Alice();
//	for(auto it:res)std::cout<<it.first<<' '<<it.second<<'\n';
//	return 0;
//}

Bob.cpp

#include<bits/stdc++.h>
long long Bob(std::vector<std::pair<int, int>> V){
 	long long p=-1,q=-1;
 	for(auto it:V){
 		if(it.first<it.second)std::swap(it.first,it.second);
 		it.first--,it.second--;
 		if(p==-1)p=it.first,q=it.second;
 		else if(p/std::__gcd(p,(long long)it.first)<=3e18/it.first){
 			while(q%it.first!=it.second)q+=p;
 			p=p/std::__gcd(p,(long long)it.first)*it.first;
		}
	}
	return q;
}
//std::mt19937_64 fdsafsdfasdfasdfsdafsdafsdafsadfsadafsdfsa(time(0));
//int main(){
//	freopen("C.txt","r",stdin);
//	std::vector<std::pair<int,int> >pp;
//	int x,y;
//	while(std::cin>>x>>y)if(fdsafsdfasdfasdfsdafsdafsdafsadfsadafsdfsa()%2)pp.push_back({x,y});
//	std::cout<<Bob(pp)<<'\n';
//	return 0;
//}

luogu version

#include<bits/stdc++.h>
long long setN(int n); 
std::vector<std::pair<int, int>> Alice(){
	long long n=setN(5000);
	std::vector<std::pair<int,int> >res;
	for(int i=1;i<=4999;i++)res.push_back({i+1,n%i+1});
	return res;
}
long long Bob(std::vector<std::pair<int, int>> V){
 	long long p=-1,q=-1;
 	for(auto it:V){
 		if(it.first<it.second)std::swap(it.first,it.second);
 		it.first--,it.second--;
 		if(p==-1)p=it.first,q=it.second;
 		else if(p/std::__gcd(p,(long long)it.first)<=3e18/it.first){
 			while(q%it.first!=it.second)q+=p;
 			p=p/std::__gcd(p,(long long)it.first)*it.first;
		}
	}
	return q;
}

NOI 2024

集合

注意到存在某个映射后相同等价于每个值的出现位置集合的集合相同。

同时注意到若 \((l,r)\) 合法,则 \((l,r-1),(l+1,r)\) 合法。

因此可以双指针预处理每个 \(r\) 对应的最小 \(l\),时间复杂度 \(O(n+q)\)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef unsigned long long ull;
typedef long long ll;
const int N=2e5+5;
const int M=6e5+5;
ull F(ull x){
	x^=x<<13;
	x^=x>>7;
	x^=x<<17;
	x=x*x*x*1145141+x*x*1333331+x*1331;
	return x;
}
int n,m,q,a[N][3],b[N][3],l[N];
ull sa[M],ha,sb[M],hb;
void slv(int _csid,int _csi){
	scanf("%d %d %d",&n,&m,&q);
	rep(i,1,n)rep(j,0,2)scanf("%d",&a[i][j]);
	rep(i,1,n)rep(j,0,2)scanf("%d",&b[i][j]);
	for(int i=1,j=1;i<=n;i++){
		rep(p,0,2){
			ha-=F(sa[a[i][p]]);
			sa[a[i][p]]+=F(i);
			ha+=F(sa[a[i][p]]);
			hb-=F(sb[b[i][p]]);
			sb[b[i][p]]+=F(i);
			hb+=F(sb[b[i][p]]);
		}
		while(ha!=hb){
			rep(p,0,2){
				ha-=F(sa[a[j][p]]);
				sa[a[j][p]]-=F(j);
				ha+=F(sa[a[j][p]]);
				hb-=F(sb[b[j][p]]);
				sb[b[j][p]]-=F(j);
				hb+=F(sb[b[j][p]]);
			}
			j++;
		}
		l[i]=j;
	}
	rep(_,1,q){
		int L,R;
		scanf("%d %d",&L,&R);
		if(l[R]<=L)puts("Yes");
		else puts("No");
	}
}
void main(){
	int T=1,csid=0;
//	scanf("%d",&csid);
//	scanf("%d",&T);
	rep(i,1,T)slv(csid,i);
}
}
int main(){
	string __name="";
	if(__name!=""){
		freopen((__name+".in").c_str(),"r",stdin);
		freopen((__name+".out").c_str(),"w",stdout);
	}
	ax_by_c::main();
	return 0;
}

NOIP 2024

NOIP 2024 补题记录

ZJOI 2025

追忆

场上 * 了,被这题结束赛季了。

首先这个问题不弱于可达性判断,于是先用 bitset \(O(\frac{nm}{\omega})\) 求出可达性。

考虑 AB 性质,考虑分块,维护 \(mx_{i,j}\) 为从 \(i\) 出发能够到达且编号在第 \(j\) 块的点的 \(b\) 的最大值。

考虑正解,考虑在 AB 的基础上操作分块,设分块长度为 \(l_1\),操作分块长度为 \(l_2\),则单次重构复杂度为 \(O(\frac{nm}{l_1})\),单次询问复杂度为 \(O(\frac{n}{l_1}+l_1+l_2)\),总复杂度为 \(O(\frac{nmq}{l_1l_2}+ql_1+ql_2)\)

考虑固定 \(l_1\),取 \(l_2=(\frac{nm}{l_1})^\frac{1}{2}\),可得复杂度为 \(O(ql_1+q(\frac{nm}{l_1})^\frac{1}{2})\)

\(l_1=(nm)^\frac{1}{3}\),可得复杂度为 \(O(q(nm)^\frac{1}{3})\)

总复杂度 \(O(\frac{nm}{\omega}+q(nm)^\frac{1}{3})\)

#include<bits/stdc++.h>
using namespace std;
namespace ax_by_c{
typedef long long ll;
const int N=1e5+5;
const int B=2714+5;
const int C=2154+5;
int n,m,q;
int a[N],b[N],pos[N];
int op_[N],x_[N],y_[N],l_[N],r_[N];
int l_1,l_2,blk,bl[C],br[C],col[N],mx[N][C];
int nds[N*2],idx;
bool mk[N];
vector<int>g[N];
bitset<N>f[N];
void slv(){
	scanf("%d %d %d",&n,&m,&q);
	l_1=cbrt((ll)n*m),l_2=max(sqrt((ll)n*m/l_1),1.0);
	blk=0;
	while(br[blk]!=n){
		blk++;
		bl[blk]=br[blk-1]+1;
		br[blk]=min(bl[blk]+l_1-1,n);
		for(int i=bl[blk];i<=br[blk];i++)col[i]=blk;
	}
	for(int i=1;i<=n;i++){
		g[i].clear();
		f[i]=0;
		f[i][i]=1;
	}
	for(int i=1,u,v;i<=m;i++){
		scanf("%d %d",&u,&v);
		g[u].push_back(v);
	}
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),pos[a[i]]=i;
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	for(int i=1;i<=q;i++){
		scanf("%d",&op_[i]);
		if(op_[i]==1)scanf("%d %d",&x_[i],&y_[i]);
		if(op_[i]==2)scanf("%d %d",&x_[i],&y_[i]);
		if(op_[i]==3)scanf("%d %d %d",&x_[i],&l_[i],&r_[i]);
	}
	for(int u=n;u>=1;u--)for(auto v:g[u])f[u]|=f[v];
	for(int l=1;l<=q;l++){
		int r=min(l+l_2-1,q);
		for(int i=l;i<=r;i++)if(op_[i]<=2)nds[++idx]=x_[i],nds[++idx]=y_[i],mk[x_[i]]=mk[y_[i]]=1;
		for(int u=n;u>=1;u--){
			for(int i=1;i<=blk;i++)mx[u][i]=0;
			if(!mk[u])mx[u][col[a[u]]]=b[u];
			for(auto v:g[u])for(int i=1;i<=blk;i++)mx[u][i]=max(mx[u][i],mx[v][i]);
		}
		for(int i=l,ans;i<=r;i++){
			if(op_[i]==1){
				swap(a[x_[i]],a[y_[i]]);
				pos[a[x_[i]]]=x_[i],pos[a[y_[i]]]=y_[i];
			}
			if(op_[i]==2)swap(b[x_[i]],b[y_[i]]);
			if(op_[i]==3){
				ans=0;
				if(col[r_[i]]-col[l_[i]]<=1){
					for(int j=l_[i];j<=r_[i];j++)if(f[x_[i]][pos[j]])ans=max(ans,b[pos[j]]);
				}
				else{
					for(int j=l_[i];j<=br[col[l_[i]]];j++)if(f[x_[i]][pos[j]])ans=max(ans,b[pos[j]]);
					for(int j=bl[col[r_[i]]];j<=r_[i];j++)if(f[x_[i]][pos[j]])ans=max(ans,b[pos[j]]);
					for(int j=col[l_[i]]+1;j<=col[r_[i]]-1;j++)ans=max(ans,mx[x_[i]][j]);
					for(int j=1;j<=idx;j++)if(f[x_[i]][nds[j]]&&l_[i]<=a[nds[j]]&&a[nds[j]]<=r_[i])ans=max(ans,b[nds[j]]);
				}
				printf("%d\n",ans);
			}
		}
		for(int i=1;i<=idx;i++)mk[nds[i]]=0;
		idx=0;
		l=r;
	}
}
void main(){
	int c_,T;
	scanf("%d %d",&c_,&T);
	while(T--)slv();
}
}
int main(){
	ax_by_c::main();
	return 0;
}

APIO 2025

哈希冲突

考场上两个关键点一个都没想对,成功获得 52 分。/dk

大致思路肯定是构造一个得数非零的集合然后往下分治,分到两个数后就得到了答案的一个倍数,试除即可得出答案。

然后考场上同时使用了生日悖论和分三块。

实际上有确定的得数非零集合:类似 BSGS,取 \(A=\{1,2,\dots,X\},B=\{X+1,2X+1,\dots,10^9+1\}\),则 \(S=\{x\mid x=q-p,p\in A,q\in B\}\) 中包含了 \([1,10^9]\) 的所有数,因此得数非零。

卡个常:由于只需要关心答案的倍数,所以只需构造 \([5\times 10^8+1,10^9]\) 中的数,变为 \(A=\{1,2,\dots,X\},B=\{5\times 10^8+1+X,2X+1,\dots,10^9+1\}\)

然后就构造好了,考虑分治。先要保证 \(A,B\) 内部得数为零,注意到它们都是等差的,可以用类似的构造方法构造集合进行判断,如果得数非零规约到新构造的集合即可。

然后考虑每次将较大集合分两块并判断哪一块和另一集合组合后得数非零,共需 \(\frac{\lvert A\rvert}{2}+\lvert B\rvert(\lvert A\rvert\ge\lvert B\rvert)\) 的代价。

这个东西极端情况就是轮流做,也就是用 \(\lvert A\rvert+\frac{3\lvert B\rvert}{2}\) 的代价各自减半,总代价即为 \(2\lvert A\rvert+3\lvert B\rvert\)

平衡一下,取 \(X=27306\) 可以通过。

#include<bits/stdc++.h>
int hack();
long long collisions(std::vector<long long> x);
#define rep(i,l,r) for(int qwp=(l),pwq=(r),i=qwp;i<=pwq;i++)
#define per(i,r,l) for(int qwp=(l),pwq=(r);i=pwq;i>=qwp;i--)
#define repll(i,l,r) for(ll qwp=(l),pwq=(r),i=qwp;i<=pwq;i++)
#define perll(i,r,l) for(ll qwp=(l),pwq=(r);i=pwq;i>=qwp;i--)
#define pb push_back
#define clr clear
using namespace std;
typedef long long ll;
const ll L=5e8+1;
const ll R=1e9;
const ll X=27306;
vector<ll>A,B,C,P,Q;
void Init(){
    A.clr(),B.clr();
    repll(i,1,X)A.pb(i);
    B.pb(L+X);
    while(B.back()<=R)B.pb(B.back()+X);
}
bool check(vector<ll> a){
    if((int)a.size()<=1)return 0;
    ll L=(a.size()-1)/2+1,R=a.size()-1,X=sqrt(R-L+1),d=a[1]-a[0];
    P.clr(),Q.clr();
    repll(i,1,X)P.pb(i);
    Q.pb(L+X);
    while(Q.back()<=R)Q.pb(Q.back()+X);
    ll t=Q.back();
    Q.pop_back(),Q.pb(min(t,R+1));
    C.clr();
    for(auto it:P)C.pb(it*d);
    for(auto it:Q)C.pb(it*d);
    if(collisions(C)){
        A.clr(),B.clr();
        for(auto it:P)A.pb(it*d);
        for(auto it:Q)B.pb(it*d);
        return 1;
    }
    return 0;
}
int hack(){
    Init();
    while(check(A)||check(B));
    while(A.size()>1||B.size()>1){
        if(A.size()<B.size())swap(A,B);
        P.clr();
        rep(i,0,A.size()/2-1)P.pb(A[i]);
        C.clr();
        for(auto it:P)C.pb(it);
        for(auto it:B)C.pb(it);
        if(collisions(C))A=P;
        else{
            P.clr();
            rep(i,A.size()/2,A.size()-1)P.pb(A[i]);
            A=P;
        }
    }
    ll x=abs(A[0]-B[0]),res=x;
    for(ll i=2;i*i<=x;i++){
        if(x%i==0){
            bool F=1;
            while(x%i==0){
                C.clr(),C.pb(1),C.pb(res/i+1);
                if(F&&collisions(C))res/=i;
                else F=0;
                x/=i;
            }
        }
    }
    if(x!=1){
        C.clr(),C.pb(1),C.pb(res/x+1);
        if(collisions(C))res/=x;
    }
    return res;
}

转杆

把数放到一个 \(0\)\(24999\) 的环上,那么两点贡献便是它们之间的距离。

注意到两个点互相在圆的两侧时对其它任意点贡献和都最大,可以直接删去。

依次将排序后第 \(i+\lfloor\frac{n}{2}\rfloor\) 个点与第 \(i\) 个配对即可,因为两边都有一半的点所以答案不减。

#include<bits/stdc++.h>
using namespace std;
void rotate(std::vector<int> t, int x);
struct node{
	int pos,id;
};
bool cmp(node x,node y){
	return x.pos<y.pos;
}
vector<node>a;
vector<int>b;
void energy(int n, std::vector<int> v){
	a.clear();
	for(int i=0;i<n;i++)a.push_back({v[i],i});
	sort(a.begin(),a.end(),cmp);
	for(int i=0,j=n/2;i<n/2;i++,j++){
		b.clear();
		b.push_back(a[j].id);
		rotate(b,(a[i].pos+25000)-a[j].pos+100000);
	}
}

NOI 2025

集合(代写)

posted @ 2025-05-02 07:49  ax_by_c  阅读(116)  评论(0)    收藏  举报