《The 2023 Guangdong Provincial Collegiate Programming Contest》vp记录

队伍配置:

\(Shui\_dream\)

\(gaosichensb\)

和我这个菜鸡。

膜拜另外两个大佬

赛况:

\(PS:\) 看高二的在那边打感觉挺有趣的我们也跑过来打了。

首先我把 \(A\) 签到题给签了,然后去看 \(D\)\(gsc\) 去看 \(C\) ,这时候 \(lyq\) 大佬还没有加入战场,还在调自己的题,不过问题不大。

我很快看出了 \(D\) 的贪心,然后这时候 \(lyq\) 加入战场,直接开 \(F\)\(\%\%\%\) 。我很快写完 \(D\) ,结果死活过不去,吃了两发罚时还没有做出来, \(gsc\) 也发现自己看错题了,然后 \(lyq\) 已经开码 \(F\) 的树套树了。

没过多久 \(gsc\)\(C\) 写完了,过来帮我调 \(D\) ,然后我看 \(I\) 很多人过了,就去写 \(I\) ,让 \(gsc\) 坐牢帮我调题。然后很快 \(lyq\) 的树套树写好了,结果发现 \(\log^2\) 跑不过去,只好换线段树二分。然后我很快写了 \(I\) ,结果又是罚时,有没有过,然后又是坐牢调题。

过了一会, \(gsc\) 说他帮我找到了 \(hack\) 数据,然后我自己就在那边改, \(gsc\) 又去帮我调 \(I\) ,然后我也不知道他怎么改,帮我过掉了 \(I\) 。然后我改我的 \(D\) ,结果还是过不去,吃了 \(4\) 发罚时了,恼羞成怒。拍案而起,润去写 \(B\)

一个小时多一点的时候, \(lyq\) 大佬凭借强大的码力通过了那个大数据结构题,然后来帮忙我写 \(D\)

过了 \(20\) 分钟, \(gsc\)\(K\) 切了,然后又过了 \(10\) 分钟,我把 \(B\) 写了。

这时候改签的到差不多签完了,还剩 \(D,E\) 是比较可做的题。

这时候 \(lyq\) 写好了 \(D\) ,但是没过,让我帮他调,帮他调的时候我突然意识到自己哪里写错了,然后把我的 \(D\) 改了一下交上去就过了,这样我们就还差 \(E\) ,就基本上可以打卡下班了。

一开始 \(E\) 我以为是 \(sa\) ,然后乱搞,然后 \(lyq\) 大佬 \(trie\) 树排排序,然后二分就秒了,他写了差不多一个小时写完了,这时候赛事排名已经来到了 \(29\)

这时候 \(gsc\) 大佬在做 \(M\) ,他直接现场学习旋转卡壳,然后后来发现假了。

我就看看 \(G\) 看看 \(H\) 发现都不会做,然后摆烂,提前下班。

然后就结束了,最后排名这鸟样。

image

这把 \(D\) 属实是有点坑逼了。

来写写题解:

\(A\)

这题还要题解???

点击查看代码
#include<bits/stdc++.h>
typedef long long Ll;

using namespace std;
const int MAXN=1e5+10;
int T,sta,n,a[MAXN],vis[MAXN];
int main () {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&sta);
		scanf("%d",&n);
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=n;++i) {
			scanf("%d",&a[i]);
			vis[a[i]]=1;
		}
		int ed;
		scanf("%d",&ed);
		int ans=0;
		for(int i=sta;i<=ed;++i) ans+=(!vis[i]);
		printf("%d\n",ans);
	}
	return 0;
}

\(B\)

记一个状态 \(f_i\) 表示前 \(i\) 个选完且满足了那些已经结尾的区间且第 \(i\)必选的最小代价。

\(f_i=\min f_j+a_i\)

如果一个区间结束了,那么这个区间左端点左边的那些决策的都不能选,赋值成无限大即可。

然后我就写了一个线段树去优化他,实际上不用,直接单调队列即可。

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=5e5+10;
const LL inf=1e18+10;
int T,n;
int a[MAXN];
vector<int> e[MAXN];
struct ddl {
	LL a;
	LL lb;
}tr[MAXN*4];
void psup(int u) {
	tr[u].a=min(tr[(u<<1)].a,tr[(u<<1|1)].a);
}
void build(int u,int l,int r) {
	tr[u].lb=0;
	if(l==r) {
		tr[u].a=inf;
		return ;
	}
	int mid=(l+r)/2;
	build((u<<1),l,mid);
	build((u<<1|1),mid+1,r);
	psup(u);
}
void zx(int x) {
	tr[x].lb=1;
	tr[x].a=inf;
}
void psdn(int u) {
	if(tr[u].lb) {
		zx((u<<1));
		zx((u<<1|1));
		tr[u].lb=0;
	}
}
void update(int u,int l,int r,int x,LL y) {
	if(l>x||r<x) return ;
	if(l==r) {
		tr[u].a=min(tr[u].a,y);
		return ;
	}
	int mid=(l+r)/2;
	psdn(u);
	update((u<<1),l,mid,x,y);
	update((u<<1|1),mid+1,r,x,y);
	psup(u);
}
void modify(int u,int l,int r,int x,int y) {
	if(l>y||r<x) return ;
	if(l>=x&&r<=y) {
		zx(u);
		return ;
	}
	int mid=(l+r)/2;
	psdn(u);
	modify((u<<1),l,mid,x,y);
	modify((u<<1|1),mid+1,r,x,y);
	psup(u);
}
LL query(int u,int l,int r,int x,int y) {
	if(l>y||r<x) return inf;
	if(l>=x&&r<=y) return tr[u].a;
	int mid=(l+r)/2;
	psdn(u);
	return min(query((u<<1),l,mid,x,y),query((u<<1|1),mid+1,r,x,y));
}
int main () {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;++i) {
			scanf("%d",&a[i]);
			e[i].clear();
		}
		int q;
		scanf("%d",&q);
		for(int i=1;i<=q;++i) {
			int l,r;
			scanf("%d%d",&l,&r);
			e[r].push_back(l);
		}
		build(1,0,n);
		update(1,0,n,0,0);
		for(int i=1;i<=n;++i) {
			LL xi=query(1,0,n,0,i-1);
			for(auto t:e[i]) {
				modify(1,0,n,0,t-1);
			}
			update(1,0,n,i,xi+a[i]);
		}
		printf("%lld\n",query(1,0,n,0,n));
	}
	return 0;
}

\(C\)

直接贪心即可,排个序,从大往小选。

\(D\)

贪心,记 \(c_i=b_i-a_i\) 那么我们把 \(a\) 数组按 \(c\) 排序,然后能尽量选就选,然后判一下情况即可。

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=5e5+10;
int T,n,m;
struct ddl {
	int a,b,c;
}a[MAXN];
bool cmp(ddl a,ddl b) {
	return a.c>b.c;
}
int main () {
	scanf("%d",&T);
	while(T--) {
		scanf("%d%d",&n,&m);
		LL sum=0;
		for(int i=1;i<=n;++i) {
			scanf("%d%d",&a[i].a,&a[i].b);
			a[i].c=a[i].b-a[i].a;
			sum+=a[i].a;
		}
		if(n==1) {
			printf("%d\n",a[1].b);
			continue;
		}
		sort(a+1,a+1+n,cmp);
		int p=m-n,ll=min(p,n),lt=0;
		for(int i=1;i<=ll;++i) {
			if(a[i].c<0) break;
			sum+=a[i].c; lt=i;
		}
		if(lt==n-1) {
			sum+=a[n].c;
			sum=max(sum,sum-a[n].c-a[n-1].c);
		}
		printf("%lld\n",sum);
	}
	return 0;
}

\(E\)

给你 \(n\) 个串,选 \(k\) 个串,使其中两两 \(lcp\) 的最大最小。

一开始我以为选数一定是相邻的最优,然后想用 \(sa\) 乱搞,实则不然,你选不相邻的可以使得可能的最大小一点。

看到最大最小我们可以考虑二分我们最后的答案的字典序,就是把所有串排序,然后答案只可能是他们的前缀,然后二分这些前缀就可以了。

每次能选就选,因为肯定是当前穿串与上一个串越远越好,然后这题就做完了。

不过还有一个细节,怎么排序,可以用字典树排序,当然你用 \(sa\) 也不是不行。

\(F\)

其实我们就是要找到左边或者右边的最远到哪里。

我们首先可以二分,但是如果直接二分加动态开点时间复杂度是 \(O(k\log^2 n)\)

可以直接线段树上二分可以做到 \(O(k\log n)\)

我写的代码:

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
 
using namespace std;
const int MAXN=1e5+10;
int TT;
int n,m;
int val[MAXN],col[MAXN];
struct tr_arr {
	LL tr[MAXN];
	int lowbit(int x) {
		return x&(-x);
	}
	void update(int x,int v) {
		for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=v;
	}
	LL qry(int x) {
		LL res=0;
		for(int i=x;i;i-=lowbit(i)) res+=tr[i];
		return res;
	}
	LL query(int l,int r) {
		return qry(r)-qry(l-1);
	}
	void clear() {
		for(int i=1;i<=n;++i) tr[i]=0;
	}
}T;
struct daduoli {
	int ls,rs,s;
}tr[MAXN*40];
int rt[MAXN],cnt,k;
void update(int &u,int l,int r,int x,int y) {
	if(!u) u=++cnt;
	tr[u].s+=y;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) update(tr[u].ls,l,mid,x,y);
	else update(tr[u].rs,mid+1,r,x,y);
}
int query(int u,int l,int r,int x,int y) {
	if(!u||l>y||r<x) return 0;
	if(l>=x&&r<=y) return tr[u].s;
	int mid=(l+r)/2;
	return query(tr[u].ls,l,mid,x,y)+query(tr[u].rs,mid+1,r,x,y); 
}
int a[MAXN],b[MAXN];
int qrL(int l,int r,int x) {
	if(l==r) return l;
	int mid=(l+r)/2;
	int ls=0;
	for(int i=1;i<=k;++i) ls+=tr[tr[a[i]].rs].s;
	if((n-mid+1)<=x+ls) {
		for(int i=1;i<=k;++i) a[i]=tr[a[i]].ls;
		return qrL(l,mid,x+ls);
	}
	else {
		for(int i=1;i<=k;++i) a[i]=tr[a[i]].rs;
		return qrL(mid+1,r,x);
	}
}
int qrR(int l,int r,int x) {
	if(l==r) return l;
	int mid=(l+r)/2;
	int ls=0;
	for(int i=1;i<=k;++i) ls+=tr[tr[a[i]].ls].s;
	if((mid+1)<=x+ls) {
		for(int i=1;i<=k;++i) a[i]=tr[a[i]].rs;
		return qrR(mid+1,r,x+ls);
	}
	else {
		for(int i=1;i<=k;++i) a[i]=tr[a[i]].ls;
		return qrR(l,mid,x);
	}
}
int main () {
	scanf("%d",&TT);
	while(TT--) {
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) {
			scanf("%d",&col[i]);
		}
		for(int i=1;i<=n;++i) {
			scanf("%d",&val[i]);
			T.update(i,val[i]);
			update(rt[col[i]],0,n+1,i,1);
		}
		int opt,x;
		for(int i=1;i<=m;++i) {
			scanf("%d%d%d",&opt,&x,&k);
			if(opt==1) {
				update(rt[col[x]],0,n+1,x,-1);
				col[x]=k;
				update(rt[col[x]],0,n+1,x,1);
			}
			if(opt==2) {
				T.update(x,k-val[x]);
				val[x]=k;
			}
			if(opt==3) {
				for(int j=1;j<=k;++j) {
					scanf("%d",&a[j]);
					a[j]=rt[a[j]]; b[j]=a[j];
				}
				int sum=0;
				for(int j=1;j<=k;++j) {
					sum+=query(a[j],0,n+1,x+1,n);
				}
				sum=((n+1)-x)-sum;
				int L=qrL(0,n+1,sum); ++L;
				sum=0;
				for(int j=1;j<=k;++j) {
					a[j]=b[j];
					sum+=query(a[j],0,n+1,1,x-1);
				}
				sum=(x-1)-sum+1;
				int R=qrR(0,n+1,sum); --R;
				printf("%lld\n",T.query(L,R));
			}
		}
		for(int i=1;i<=cnt;++i) {
			tr[i].ls=tr[i].rs=tr[i].s=0;
		}
		for(int i=1;i<=n;++i) rt[i]=0;
		cnt=0;
		T.clear();
	}
	return 0;
}

\(lyq\) 的代码,但是不知道为什么没过

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=1e5+10;
int TT;
int n,m;
int val[MAXN],col[MAXN];
struct tr_arr {
	LL tr[MAXN];
	int lowbit(int x) {
		return x&(-x);
	}
	void update(int x,int v) {
		for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=v;
	}
	LL qry(int x) {
		LL res=0;
		for(int i=x;i;i-=lowbit(i)) res+=tr[i];
		return res;
	}
	LL query(int l,int r) {
		return qry(r)-qry(l-1);
	}
	void clear() {
		for(int i=1;i<=n;++i) tr[i]=0;
	}
}T;
struct daduoli {
	int ls,rs,s;
}tr[MAXN*40];
int rt[MAXN],cnt,k;
void update(int &u,int l,int r,int x,int y) {
	if(!u) u=++cnt;
	tr[u].s+=y;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) update(tr[u].ls,l,mid,x,y);
	else update(tr[u].rs,mid+1,r,x,y);
}
int a[MAXN],b[MAXN];
vector<int> e;
int qrL(vector<int> p,int l,int r,int x) {
	if(l>x) return 0;
	if(r<=x) {
		int ls=0;
		for(int i=0;i<k;++i) ls+=tr[p[i]].s;
		if(ls==r-l+1) return l;
	}
	if(l==r) return 0;
	int mid=(l+r)/2;
	vector<int> bl,br;
	for(int i=0;i<k;++i) bl.push_back(tr[p[i]].ls),br.push_back(tr[p[i]].rs);
	if(x<=mid) return qrL(bl,l,mid,x);
	else {
		int res=qrL(br,mid+1,r,x);
		if(res==mid+1) {
			int ls=qrL(bl,l,mid,x);
			if(ls) res=ls;
		}
		return res;
	}
}
int qrR(vector<int> p,int l,int r,int x) {
	if(r<x) return 0;
	if(l>=x) {
		int ls=0;
		for(int i=0;i<k;++i) ls+=tr[p[i]].s;
		if(ls==r-l+1) return r;
	}
	if(l==r) return 0;
	int mid=(l+r)/2;
	vector<int> bl,br;
	for(int i=0;i<k;++i) bl.push_back(tr[p[i]].ls),br.push_back(tr[p[i]].rs);
	if(x>mid) return qrR(br,mid+1,r,x);
	else {
		int res=qrR(bl,l,mid,x);
		if(res==mid) {
			int ls=qrR(br,mid+1,r,x);
			if(ls) res=ls;
		}
		return res;
	}
}
int main () {
	scanf("%d",&TT);
	while(TT--) {
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) {
			scanf("%d",&col[i]);
		}
		for(int i=1;i<=n;++i) {
			scanf("%d",&val[i]);
			T.update(i,val[i]);
			update(rt[col[i]],1,n,i,1);
		}
		int opt,x;
		for(int i=1;i<=m;++i) {
			scanf("%d%d%d",&opt,&x,&k);
			if(opt==1) {
				update(rt[col[x]],1,n,x,-1);
				col[x]=k;
				update(rt[col[x]],1,n,x,1);
			}
			if(opt==2) {
				T.update(x,k-val[x]);
				val[x]=k;
			}
			if(opt==3) {
				e.clear();
				for(int j=1;j<=k;++j) {
					scanf("%d",&a[j]);
					a[j]=rt[a[j]];
					e.push_back(a[j]);
				}
				int L=qrL(e,1,n,x);
				int R=qrR(e,1,n,x);
				printf("%lld\n",T.qry(R)-T.qry(L-1));
			}
		}
		for(int i=1;i<=cnt;++i) {
			tr[i].ls=tr[i].rs=tr[i].s=0;
		}
		for(int i=1;i<=n;++i) rt[i]=0;
		cnt=0;
		T.clear();
	}
	return 0;
}

\(G\)

首先对于这种有关位运算的,我们有一个经典 \(trick\) ,很多类型的位运算他们至多改变次数是 \(\log n\) 次, \(\&\) 就是如此。

所以我们考虑到他的前缀 \(\&\) 值,和后缀 \(\&\) 值最多只有 \(\log n\) 个不同。

我们考虑记改变 \(\&\) 值的位置为关键点。

那么现在有三种交换,不过你交换你得有断点才有意义,否则你连断点都不知道在哪里,你交换了又有什么用呢。

  • 非关键点和非关键点

显然如果两个非关键点交换不会带来任何贡献。

  • 关键点和关键点

这个我们可以直接枚举,因为两边都只有 \(\log n\) 个,所以你枚举至多只有 \(\log^2 n\) 个。总时间复杂度 \(O(n\log^2 n)\)

  • 关键点和非关键点

考虑枚举前缀关键点,那么贡献就是 \(f(1,i-1)\& f(i+1,k)\& a_j+f(k+1,n)\& a_i\)

后面那个式子不管 \(j\) 是什么都是固定的,所以我们现在就是要找到最大的, \(f(1,i-1)\& f(i+1,k)\& a_j\) ,因为 \(f(1,i-1)\) 至多只有 \(\log n\) 个,而对于每个关键点至多有 \(\log n\)\(f(i+1,k)\) ,所以 \(f(1,i-1)\& f(i+1,k)\) 至多只有 \(\log^2 n\) 个,所以对于每个这个东西都暴力枚举即可。后缀关键点同理

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=1e5+10;
int T;
int n,a[MAXN];
int st[MAXN][20],logg[MAXN];
void init() {
	for(int i=2;i<=n;++i) logg[i]=logg[i/2]+1;
	for(int i=1;i<=logg[n];++i) {
		int R=(n-(1<<i))+1;
		for(int j=1;j<=R;++j) {
			st[j][i]=(st[j][i-1]&st[j+(1<<(i-1))][i-1]);
		}
	}
}
int query(int l,int r) {
	if(l>r) return (1ll<<31)-1;
	int k=r-l+1; k=logg[k];
	return (st[l][k]&(st[r-(1<<k)+1][k]));
}
unordered_map<int,vector<int> > f;
unordered_map<int,vector<int> > g;
int get_g(int v,int i) {
	if(!g.count(v)) {
		vector<int> ls;
		ls.resize(n+2);
		for(int i=n;i>=1;--i) ls[i]=max(ls[i+1],(v&a[i]));
		g[v]=ls;
	}
	return g[v][i];
}
int get_f(int v,int i) {
	if(!f.count(v)) {
		vector<int > ls;
		ls.resize(n+1);
		for(int i=1;i<=n;++i) ls[i]=max(ls[i-1],(v&a[i]));
	}
	return f[v][i];
}
int main () {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;++i) {
			scanf("%d",&a[i]);
			st[i][0]=a[i];
		}
		init();
		//get imp
		vector<int> pre,suf;
		int x=(1ll<<31)-1;
		for(int i=1;i<=n;++i) {
			if((x&a[i])!=x) pre.push_back(i);
			x&=a[i];
		}
		x=(1ll<<31)-1;
		for(int i=n;i>=1;--i) {
			if((x&a[i])!=x) suf.push_back(i);
			x&=a[i];
		} 
		int ans=0;
		//no swap
		for(int i=1;i<n;++i) {
			ans=max(ans,query(1,i)+query(i+1,n));
		}
		//imp and imp
		for(int i=1;i<n;++i) {
			for(auto l:pre) {
				if(l>i) break;
				for(auto r:suf) {
					if(r<=i) break;
					ans=max(ans,(query(1,l-1)&query(l+1,i)&a[r])+(query(i+1,r-1)&query(r+1,n)&a[l]));
				}
			}
		}
		//pre imp and suf iimp
		g.clear();
		for(int i=1;i<=n;++i) {
			for(auto l:pre) {
				if(l>i) break;
				int val=(query(i+1,n)&a[l]);
				int v=get_g((query(1,l-1)&query(l+1,i)),i+1);
				ans=max(ans,val+v);
			}
		}
		
		//suf imp and pre iimp
		f.clear();
		for(int i=n;i>=1;--i) {
			for(auto r:suf) {
				if(r<=i) break;
				int val=(query(1,i)&a[r]);
				int v=get_g((query(i+1,r-1)&query(r+1,n)),i);
				ans=max(ans,val+v);
			}
		}
		
		printf("%d\n",ans);
	}
	return 0;
}

\(H\)

首先对于这样的二元组我们有一种感觉,就是在他们之间建边,把它们转化成图论。

不过由于考试的时候没看到只有 \(1,2\) 就压根没有思路。

由于每个位置只会被最后一次操作影响我们考虑倒着做,先取哪些位置。

我们考虑有哪些操作

  • 假如有一个操作是 \(x,2,y,2\)

那么这个操作一定是最优的,他一定是第一个选择的

  • 如果有一个操作是 \(x,1,y,1\)

那么一定是最劣的,一定是最后选择的。

  • 如果有一个操作是 \(x,2,y,1\) 或者 \(x,1,y,2\)

那么这是无法辨别的,我们考虑对于修改成 \(1\) 的向修改成 \(2\) 的位置连一个边。

这样从一个点(这个点选 \(1\) )到达的所有点都是能被锁定成 \(2\) 的,然后我们就是要让锁定成 \(2\) 的最多。

但是图不好做,我们缩成 \(DAG\) 图,因为一个 \(SCC\) 中点可以互相到达,所以只要有一个点被遍历,那么整个 \(SCC\) 都可以被遍历。

所以我们就从入度为 \(0\) 的边开始遍历,然后所有入度为 \(0\) 的边都遍历一下就好了。

注意事项:对于被 \(x,2,y,2\) 这样操作锁定成 \(2,2\) 的点,在遍历过程中我们优先遍历这些点,因为这些点一定是 \(2\)

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

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=5e5+10;
int T;
int n,m;
int o[MAXN][4];
vector<int> ans,vp,ls;
struct ddl {
	int t,c;
};
vector<ddl> e[MAXN];
void add(int f,int t,int c) {
	e[f].push_back({t,c});
}
int dfn[MAXN],low[MAXN],ind[MAXN],tot,cnt;
int cir,scc[MAXN],val[MAXN];
bool vis[MAXN];
void tarjan(int u) {
	dfn[u]=low[u]=++cnt;
	ind[++tot]=u; vis[u]=1;
	for(auto t:e[u]) {
		int T=t.t;
		if(!dfn[T]) {
			tarjan(T);
			low[u]=min(low[u],low[T]);
		}
		else if(vis[T]) low[u]=min(low[u],dfn[T]);
	}
	if(dfn[u]==low[u]) {
		++cir;
		while(1) {
			scc[ind[tot]]=cir;
			vis[ind[tot]]=0;
			--tot;
			if(ind[tot+1]==u) break;
		}
	}
}
int deg[MAXN];
bool sf[MAXN];
void dfs(int u) {
	if(sf[u]) return ;
	sf[u]=1;
	for(auto t:e[u]) {
		int T=t.t;
		ls.push_back(t.c);
		dfs(T);
	}
}
void vmain() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i) {
		for(int j=0;j<4;++j) scanf("%d",&o[i][j]);
		if(o[i][1]==1&&o[i][3]==1) {
			ans.push_back(i);
		}
		if(o[i][1]==1&&o[i][3]==2) add(o[i][0],o[i][2],i);
		if(o[i][1]==2&&o[i][3]==1) add(o[i][2],o[i][0],i);
		if(o[i][1]==2&&o[i][3]==2) {
			vp.push_back(i);
		}
	}
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	
	for(int i=1;i<=n;++i)  {
		for(auto t:e[i]) {
			if(scc[i]!=scc[t.t]) ++deg[scc[t.t]];
		}
	}
		
	for(auto t:vp) {
		if(!deg[scc[o[t][0]]]) dfs(o[t][0]);
		if(!deg[scc[o[t][2]]]) dfs(o[t][2]);
	}
	
	for(int i=1;i<=n;++i) if(!deg[scc[i]]) dfs(i);
	reverse(ls.begin(),ls.end());
	for(auto t:ls) {
		ans.push_back(t);
	}
	for(auto t:vp) {
		ans.push_back(t);
	}
	for(auto t:ans) {
		val[o[t][0]]=o[t][1];
		val[o[t][2]]=o[t][3];
	}
	int cc=0;
	for(int i=1;i<=n;++i) cc+=val[i];
	printf("%d\n",cc);
	for(auto t:ans) {
		printf("%d ",t);
	}
	puts("");
	
	cnt=tot=cir=0;
	ans.clear(); vp.clear(); ls.clear();
	for(int i=1;i<=n;++i) {
		e[i].clear();
		vis[i]=sf[i]=0;
		dfn[i]=low[i]=scc[i]=val[i]=deg[i]=0;
	}
}
int main () {
	scanf("%d",&T);
	while(T--) vmain();
	return 0;
}

\(I\)

二分 \(mex\) ,然后排序,时间复杂度 \(O(nm\log^2(nm))\)

实际上不需要排序,可以直接枚举所有小于等于 \(mid\) 的值,这样得到的数其实已经拍好序了。

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

我实现的没有那么精细,写了 \(\log^2\) 的。

点击查看代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=1e6+10;
int T,n,m;
int a[MAXN],b[MAXN];
struct ddl {
	int x,y;
}d[MAXN];
int cnt;
bool cmp(ddl a,ddl b) {
	if(a.x!=b.x) return a.x<b.x;
	return a.y<b.y;
}
bool check(int x) {
	cnt=0;
	for(int i=0;i<=x;++i) {
		d[++cnt].x=a[i];
		d[cnt].y=b[i];
	}
	sort(d+1,d+1+cnt,cmp);
	int y=0;
	for(int i=1;i<=cnt;++i) {
		if(y>d[i].y) return false;
		y=d[i].y;
	}
	return true;
}
int erfind() {
	int l=0,r=n*m,mid;
	while(l+1<r) {
		mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	return l;
}
int main () {
	scanf("%d",&T);
	while(T--) {
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) {
			for(int j=1;j<=m;++j) {
				int x;
				scanf("%d",&x);
				a[x]=i; b[x]=j;
			}
		}
		printf("%d\n",erfind()+1);
	}
	return 0;
}


\(J\)

题解没看太懂,不过懂大概思想就好了。

  • 首先如果序列只有一位,那么直接判断 \(x=y\) 就好了。

  • 如果序列有两位,那么肯定满足 \(a^2>x\)

我们设最高位是 \(t\) ,满足 \(t<a\)

又因为低位相同,所以 \(x-ta=y-tb\) ,所以有 \(b=\frac {y-x}t+a\)

然后我们要满足一些条件,低位 \(<a\) 。然后还要满足 \(2\le a\le A,2\le b\le B\)

因为 \(b\) 为整数,所以 \(t\) 至多只有 \(\sqrt n\) 个。

然后还要推一些式子,我不太会推,反正对着题解抄就完了。

  • 如果序列大于两位,那么这样的至多只有 \(\sqrt n\)

然后暴力枚举一下,双指针就好了,时间复杂度 \(O(\sqrt n\log n)\)

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
int T;
int x,y,A,B;
LL gao(int a,int b) {
	LL ls=x,sum=0,pw=1;
	while(ls) {
		sum+=ls%a*pw;
		ls/=a; pw*=b;
		if(sum>y) return sum;
	}
	return sum;
}
bool check(int a,int b) {
	LL xx=x,yy=y;
	while(xx&&yy) {
		if(xx%a!=yy%b) return false;
		xx/=a; yy/=b;
	}
	return (xx==yy);
}
LL ceil(LL a,LL b) {
	return (a+b-1)/b;
}
void vmain() {
	scanf("%d%d%d%d",&x,&y,&A,&B);
	//1
	if(x==y) {
		puts("YES");
		puts("2 2");
		return ;
	}
	//2
	for(LL t=1;t*t<=x&&t<=A;++t) {
		if((y-x)%t==0) {
			LL l=2,r=A;
			l=max(l,ceil(2*t+x-y,t));
			l=max(l,x/(t+1)+1);
			l=max(l,t+1);
			l=max(l,((t+1)*x-y)/(t*(t+1))+1);
			l=max(l,(t*t+x-y)/t+1);
			r=min(r,(t*B+x-y)/t);
			r=min(r,x/t);
			if(l<=r) {
				puts("YES");
				printf("%d %d\n",l,(t*l-x+y)/t);
				return ;
			}
		}
	}
	//3
	for(int a=2,b=2;a*a<=x&&a<=A;++a) {
		while(gao(a,b)<y&&b*b<=y&&b<=B) ++b;
		if(b*b>y||b>B) break;
		if(gao(a,b)==y&&check(a,b)) {
			puts("YES");
			printf("%d %d\n",a,b);
			return ;
		}
	}
	puts("NO");
}
int main () {
	scanf("%d",&T);
	while(T--) {
		vmain();
		
	}
	return 0;
}

\(K\)

暴力 \(dfs\) 即可。

posted @ 2023-09-15 18:45  daduoli  阅读(102)  评论(1)    收藏  举报