补题记录

Todo List (\(15/40\))

已抛弃

  1. [7] 满穗

可能做

  1. [13] 小孩召开法 4 AGC056B

[3] abc 猜想

注意到 \(\lfloor\frac{a^{b}}{c}\rfloor\mod c=\lfloor\frac{a^{b}-kc^{2}}{c}\rfloor\mod c=\lfloor\frac{a^{b}\mod c}{c}\rfloor\mod c\)

快速幂即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#define int long long
int a,b,c;
int power(int a,int t,int p){
	int base=a,ans=1;
	while(t){
		if(t&1){
			ans=ans*base%p;
		}
		base=base*base%p;
		t>>=1;
	}
	return ans;
}
signed main(){
	read(a,b,c);
	cout<<(power(a,b,c*c)/c+c)%c<<endl;
}

[3] 简单的排列最优化题

\(n^{2}\) 的解法是显然的

考虑如何 \(O(n)\) 做,需要我们从上一个状态转移到当前状态,我们把数和贡献分别分成 \(p_i\le i\)\(p_i\gt i\) 两部分,首先简单手摸一下可以发现每次两部分答案的增加/减小量恰好就是两部分的数字之和,而每次两部分答案显然会一个增加 \(1\),一个减小 \(1\)(排列的性质)

需要考虑的就是边界情况,边界有一个为最左端与最右端的转换,还有一个 \(p_i=i\) 时的转换,前者可以直接套式子,对于后者,因为我们只关心数量,因此考虑记录没一个时刻有几个到达该转换的值即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#define int long long
int a,b,c;
int power(int a,int t,int p){
	int base=a,ans=1;
	while(t){
		if(t&1){
			ans=ans*base%p;
		}
		base=base*base%p;
		t>>=1;
	}
	return ans;
}
int n;
int p[10000001];
int rcnt,lcnt,rtot,ltot;
int dx[10000001];
signed main(){
    read(n);
    for(int i=1;i<=n;++i){
        read(p[i]);
    }
    for(int i=1;i<=n;++i){
        if(p[i]<=i){
            lcnt++;
            ltot+=(i-p[i]);
        }
		else{
            dx[p[i]-i]++;
            rcnt++;
            rtot+=(p[i]-i);
        }
    }
    int ans=ltot+rtot,ansid=0;
    for(int i=1;i<=n-1;++i){
        rtot-=rcnt;
        rcnt-=dx[i];
        ltot+=lcnt;
        lcnt+=dx[i];
        ltot-=n-p[n-i+1]+1;
		lcnt--;
        if(p[n-i+1]>1){
			dx[p[n-i+1]+i-1]++;
			rtot+=p[n-i+1]-1;
			rcnt++;
		}
        else{
			lcnt++;
		}
        if(ltot+rtot<ans){
			ans=ltot+rtot;
			ansid=i;
		}
    }
    cout<<ansid<<" "<<ans<<endl;
}

[1] mine

设计 \(f_{i,0/1/2}\) 表示进行到第 \(i\) 位时,需要下一位是雷/不是雷,或者该位是雷的方案数

当该为是 \(0\) 时,应从上一位的 \(0\) 状态转移,并要求下一位为 \(0\)

当该位是 \(1\) 时,可以从上一位的 \(0\) 状态转移,并要求下一位为雷,或者从上一位的 \(2\) 状态转移,要求下一位为 \(0\)

当该为是 \(2\) 时,应从上一位的 \(2\) 状态转移,并要求下一位是雷

当该为是雷时,应从上一位的 \(1\) 状态转移

起始状态需要注意,起始的 \(i\) 需要特殊处理

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
string s;
int f[1000001][3];
const int p=1e9+7;
/* 0 mine : 1 mine : is mine*/
signed main(){
	cin>>s;
	if(s[0]=='0' or s[0]=='?') f[0][0]=1;
	if(s[0]=='1' or s[0]=='?') f[0][1]=1;
	if(s[0]=='*' or s[0]=='?') f[0][2]=1;
	for(int i=1;i<=s.length()-1;++i){
		if(s[i]=='0' or s[i]=='?'){
			f[i][0]+=f[i-1][0];
		}
		if(s[i]=='1' or s[i]=='?'){
			f[i][0]+=f[i-1][2];
			f[i][1]+=f[i-1][0];
		}
		if(s[i]=='2' or s[i]=='?'){
			f[i][1]+=f[i-1][2];
		}
		if(s[i]=='*' or s[i]=='?'){
			f[i][2]+=f[i-1][1]+f[i-1][2];
		}
		f[i][0]%=p;
		f[i][1]%=p;
		f[i][2]%=p;
//		cout<<f[i][0]<<" "<<f[i][1]<<" "<<f[i][2]<<endl;
	}
	cout<<(f[s.length()-1][0]+f[s.length()-1][2])%p;
}

[2] 序列

\(1\)\(n\) 枚举 \(r\) ,设 \(f_{i}\) 表示区间 \([i,r]\) 中仅出现一次的数的个数,考虑 \(r\)
\(r+1\) 的变化

  • \(a_{r+1}\) 还未出现过,则 \([1,r+1]\) 内的 \(f\) 都加 \(1\)
  • 否则记 \(a_{r+1}\) 上次出现时的下标为 \(j\),上上次出现时的下标为 \(k\),则 \([j+1,r+1]\) 内的 \(f\) 值都加 \(1\), \([k+1,j]\) 内的 \(f\) 值都减 \(1\)

序列的合法条件即为任意时刻 \(f_{i}\) 的值均大于零, 用线段树维护加减操作和区间最小值,同时记录每个值前两次出现的位置即可

这个做法是很经典的套路,可以用来统计具有某种特征的区间的数量,枚举区间右端点 ,在所有左端点维护区间的信息,即可快速统计所有区间.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
struct tree{
	int l,r;
	int lazy;
	int minn;
}t[800001];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void build(int id,int l,int r){
	t[id].l=l;t[id].r=r;
	t[id].lazy=t[id].minn=0;
	if(l==r){
		return;
	}
	int mid(l,r);
	build(tol,l,mid);
	build(tor,mid+1,r);
}
void pushdown(int id){
	if(t[id].lazy){
		t[tol].lazy+=t[id].lazy;
		t[tor].lazy+=t[id].lazy;
		t[tol].minn+=t[id].lazy;
		t[tor].minn+=t[id].lazy;
		t[id].lazy=0;
	}
}
void change(int id,int l,int r,int k){
//	cout<<"change "<<id<<" "<<l<<" "<<r<<" "<<t[id].l<<" "<<t[id].r<<" "<<k<<endl;
	if(l<=t[id].l and t[id].r<=r){
		t[id].minn+=k;
		t[id].lazy+=k;
		return;
	}
	pushdown(id);
	if(r<=t[tol].r) change(tol,l,r,k);
	else if(l>=t[tor].l) change(tor,l,r,k);
	else{
		int mid(t[id].l,t[id].r);
		change(tol,l,mid,k);
		change(tor,mid+1,r,k);
	}
	t[id].minn=min(t[tol].minn,t[tor].minn);
}
int ask(int id,int l,int r){
	if(l<=t[id].l and t[id].r<=r){
		return t[id].minn;
	}
	pushdown(id);
	if(r<=t[tol].r){
		return ask(tol,l,r);
	}
	else if(l>=t[tor].l){
		return ask(tor,l,r);
	}
	else{
		int mid(t[id].l,t[id].r);
		return min(ask(tol,l,mid),ask(tor,mid+1,r));
	}
}
map<int,int>mp;
int cnt=0;
int T,n;
int a[200001];
int last[200001],l_last[200001];
int main(){
	cin>>T;
	while(T--){
		cnt=0;
		read(n);
		build(1,1,n);
		memset(last,0,sizeof last);
		memset(l_last,0,sizeof l_last);
		mp.clear();
		for(int i=1;i<=n;++i){
			read(a[i]);
			if(!mp.count(a[i])) mp[a[i]]=++cnt;
			a[i]=mp[a[i]];
		}
		bool flag=false;
		for(int i=1;i<=n;++i){
			if(!last[a[i]]){
//				cout<<"add [1,"<<i+1<<"]"<<1<<endl;
				last[a[i]]=i;
				change(1,1,i,1);
			}
			else{
				change(1,last[a[i]]+1,i,1);
//				cout<<"add ["<<last[a[i]]+1<<","<<i+1<<"]"<<1<<endl;
				change(1,l_last[a[i]]+1,last[a[i]],-1);
//				cout<<"add ["<<l_last[a[i]]+1<<","<<last[a[i]]<<"]"<<-1<<endl;
				l_last[a[i]]=last[a[i]];
				last[a[i]]=i;
			}
			if(ask(1,1,i)<=0){
//				cout<<i<<" "<<ask(1,1,i)<<" ";
				cout<<"boring"<<endl;
				flag=true;
				break;
			}
		}
		if(!flag){
			cout<<"non-boring"<<endl;
		}
	}
}

[2] Leagcy

线段树优化建图板子题

考虑到,如果我们需要从节点 \(x\)\([l,r]\) 中的所有节点连边,我们可以考虑建一颗线段树,分别将 \(x\) 与符合要求的区间节点连边,再将区间节点与其子节点连边权为 \(0\) 的边即可

本题既有单点连接区间,也有区间连接单点,对两种情况分别建一颗线段树即可,区间连接单点则需要建儿子指向父亲的线段树

点击查看代码
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
#define int long long
struct tree{
	int l,r;
}t[400001];
int n,m,st;
int dis[1000001];
int leaf[100001];
bool vis[1000001];
#define mid(l,r) mid=((l)+(r))/2
#define tol (id*2)
#define tor (id*2+1)
const int dx=5e5;
struct edge{
	int to,w;
};
vector<edge>e[1000001];
void build(int id,int l,int r){
	t[id].l=l;t[id].r=r;
	if(l==r){
		leaf[l]=id;
		return;
	}
	int mid(l,r);
	e[id].push_back({tol,0});
	e[id].push_back({tor,0});
	e[tol+dx].push_back({id+dx,0});
	e[tor+dx].push_back({id+dx,0});
	build(tol,l,mid);
	build(tor,mid+1,r);
}
void connect(int id,int l,int r,int to,int w,int tp){
	if(l<=t[id].l and t[id].r<=r){
		if(tp){
			e[id+dx].push_back({to,w});
		}
		else{
			e[to].push_back({id,w});
		}
		return;
	}
	int mid(t[id].l,t[id].r);
	if(r<=mid) connect(tol,l,r,to,w,tp);
	else if(l>mid) connect(tor,l,r,to,w,tp);
	else{
		connect(tol,l,mid,to,w,tp);
		connect(tor,mid+1,r,to,w,tp);
	}
}
struct node{
	int id,val;
	bool operator <(const node &A)const{
		return val>A.val;
	}
};
void dij(int s){
	priority_queue<node>q;
	memset(dis,0x3f,sizeof dis);
	dis[leaf[s]+dx]=0;
	q.push({leaf[s]+dx,dis[leaf[s]+dx]});
	while(!q.empty()){
		node u=q.top();
		q.pop();
		if(vis[u.id]) continue;
		for(edge i:e[u.id]){
			if(dis[i.to]>dis[u.id]+i.w){
				dis[i.to]=dis[u.id]+i.w;
				q.push({i.to,dis[i.to]});
			}
		} 
	}
}
signed main(){
	read(n,m,st);
	build(1,1,n);
	for(int i=1;i<=m;++i){
		int op,from,to,l,r,val;
		read(op);
		if(op==1){
			read(from,to,val);
			e[leaf[from]].push_back({leaf[to],val});
		}
		else{
			read(to,l,r,val);
			connect(1,l,r,leaf[to],val,op&1);
		}
	}
	for(int i=1;i<=n;++i){
		e[leaf[i]].push_back({leaf[i]+dx,0});
		e[leaf[i]+dx].push_back({leaf[i],0});	
	}
	dij(st);
	for(int i=1;i<=n;++i){
		if(dis[leaf[i]]==0x3f3f3f3f3f3f3f3f) cout<<"-1 ";
		else cout<<dis[leaf[i]]<<" ";
	}
}

[2] DP 搬运工 2

考虑从 \(1\)\(n\) 插入所有数到序列中

这样做的话就会有一个很好的性质,就是不管这个数插到哪里,它总是最大的数,所以总会使合法的状态增加 \(1\)(除非插在两边)

反例就是当插入的这个数破坏了原来合法的一组的时候,同时会让答案减一,这样就相当于不变了

\(f_{i,j}\) 表示考虑前 \(i\) 位,合法 \(j\) 组的方案数,考虑从 \(i-1\) 转移,当我们插入 \(i\) 的时候,一共有 \(2j\) 个位置(在 \(j\) 个本来合法的位置两边插入)能够增加答案的同时破坏一个答案,一共有 \(2\) 个位置(在整个序列首尾插入)能不增加答案,其余都是增加 \(1\) 的方案

直接转移即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
int n,k;
int f[2001][2001];
const int p=998244353;
int main(){
	read(n,k);
	f[1][0]=1;f[2][0]=2;
	for(int i=3;i<=n;++i){
		for(int j=0;j<=min(i/2,k);++j){
			f[i][j]=(f[i][j]+f[i-1][j]*1ll*(2*j+2))%p;
			f[i][j+1]=(f[i][j+1]+f[i-1][j]*1ll*(i-2*j-2))%p;
		}
	}
	cout<<(f[n][k]+p)%p<<endl;
}

[2] DP 搬运工 3

首先钦定一个排列为升序

设计 \(f_{i,j,k}\) 表示另一个排列考虑到第 \(i\) 位,前 \(i\) 位还剩 \(j\) 个数没填,当前总和为 \(k\) 的方案数

容易想到,当前位可以不填,空位就会多一个,因此从 \(f_{i-1,j-1,k}\) 转移

或者填到之前存在的 \(j\) 个空位里,这里有一个非常好的性质是,无论你放在前面哪个地方,贡献都是 \(i\),所以从 \(f_{i-1,j,k-i}\) 转移

或者填到之前存在的 \(j\) 个空位里,并且让之前那 \(j\) 个没填的数分出来一个填 \(i\),这样还是那个非常好的性质,贡献为 \(2i\),从 \(f_{i-1,j+1,k-2i}\) 转移

这样做既考虑了前面数字填到后面的情况,也考虑到了后面的数字向前填的情况,是完整的,因此转移正确

因为我们钦定了一个排列为升序,不难发现,对任何一个排列的答案都一样,所以乘以 \(n!\) 即可

点击查看代码
#define int modint<998244353,int>
int n,k;
int f[51][51][51*51];
signed main(){
	read(n,k);
	f[1][0][1]=f[1][1][0]=1;
	for(int i=2;i<=n;++i){
		for(int j=0;j<=min(i-1,n-i+1);j++){
			for(int k=0;k<=i*i;k++){
				if(f[i-1][j][k]){
					f[i][j+1][k]+=f[i-1][j][k];
					f[i][j][k+i]+=f[i-1][j][k]*(j*2+1);
					if(j) f[i][j-1][k+2*i]+=f[i-1][j][k]*j*j;
				}
			}
		}
	}
	int ans=0;
	for(int i=k;i<=n*n;++i){
		ans+=f[n][0][i];
	}
	for(int i=2;i<=n;++i){
		ans*=i;
	}
	cout<<ans<<endl;
}

[6] 合并r

定义 \(f_{i,j}\) 表示已经选了 \(i\) 个数,和为 \(j\) 的方案数

引理:\(f_{i,j}=f_{i,2j}\)

证明:\(i\) 个数全部乘二即可

因此直接让 \(f_{i,j}\)\(f_{i,2j}\) 转移即可

此外也可以加入一个新的数,从 \(f_{i-1,j-1}\) 转移即可

初态 \(f_{0,0}=1\)

点击查看代码
using mint=modint<998244353>;
mint f[5001][5001];
long long n,k;
int main(){
	read(n,k);
	f[0][0]=1;
	for(int i=1;i<=n;++i){
		for(int j=i;j>=1;--j){
			f[i][j]=f[i-1][j-1];
			if(2*j<=i){
				f[i][j]+=f[i][2*j];
			}
		}
	}
	cout<<f[n][k];
}

[19] 那一天她离我而去

非常奇怪的处理方法

暴力的思路就是强行断开与根节点相连的点,然后对每一个之前与根节点直接相连的点跑一遍最短路,取最小值即为答案

然后是一个优化,考虑到我们可以对节点进行分组,一共分成两组,每次对第一组建立超级源点,对第二组建立超级汇点,然后从超级源点向超级汇点跑最短路,不难发现,这个最短路策略与原暴力方法相同,当且仅当原暴力方法对应的两个节点分别在不同的组内,所以我们只需要保证这两个点被分在不同的组里即可

下面来想分组方式,我们枚举每个节点编号二进制意义下的每一位,把该位是 \(0\) 的分为一组,是 \(1\) 的分为一组,因为题目要求,这两个点的编号不同,因此总会存在一个数位,使得两个点分别在不同的组内. 因此 \(log\ n\) 次后取最小值即为答案

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
int n,m;
struct edge{
	int to,w;
};
vector<edge>e[2000001];
struct node{
	int id,dis;
	bool operator <(const node &A)const{
		return dis>A.dis;
	}
};
int dis[2000001];
bool vis[2000001];
void dij(int s,int ed){
	priority_queue<node>q;
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;
	q.push({s,dis[s]});
	while(!q.empty()){
		node u=q.top();
		q.pop();
		if(u.id==ed) return;
		if(vis[u.id]) continue;
		for(edge i:e[u.id]){
			if(!vis[i.to] and dis[i.to]>dis[u.id]+i.w){
				dis[i.to]=dis[u.id]+i.w;
				q.push({i.to,dis[i.to]});
			}
		}
	}
}
int st=0,ed=0;
vector<edge>v;
int main(){
	int cases;read(cases);while(cases--){
		read(n,m);
		for(int i=1;i<=m;++i){
			int x,y,z;read(x,y,z);
			if(x>y) swap(x,y);
			if(x==1){
				v.push_back({y,z});
			}
			else{
				e[x].push_back({y,z});
				e[y].push_back({x,z}); 
			}
		}
		int ans=0x3f3f3f3f,now=n;
		for(int i=1;i<=n;i<<=1){
			st+=++now;ed+=++now;
			for(edge j:v){
				if(j.to&i) e[st].push_back(j);
				else e[j.to].push_back({ed,j.w});
			}
			dij(st,ed);
			ans=min(ans,dis[ed]);
		}
		if(ans==0x3f3f3f3f) cout<<-1<<endl;
		else cout<<ans<<endl;
		for(int i=1;i<=now;++i) e[i].clear();
		v.clear();
	}
}

[8] 简单的拉格朗日反演练习题

要改的时候突然发现自己已经写好了,就剩卡常了,我一看最大点 1005ms,这不随便卡卡就能过,所以就卡过了

不明白为什么加 register 和开 O3 负优化了,最后是靠评测机波动卡过去的

考虑一条边的边权为编号,记 \(f(u,v)\)\(u,v\) 在最小生成树上路径中边权最大值,容易发现答案即为

\[\max_{i=l}^{r-1} \{ f(i,i+1) \} \]

如果我们可以求得 \(f\),那就可以通过 st 表等结构快速得到答案

那么如何求得 \(f\)

这是很容易的,使用 Kruskal 重构树可以做到 \(O(n\log n)\) 求解

Kruskal 生成树不多赘述了,详见 此篇

点击查看代码
//#pragma GCC optimize(5)
#include<bits/stdc++.h>
using namespace std;
int n,m,q,tot;
namespace dsu{
	int fa[300001];
	inline void clear(){
		for( int i=1;i<=n+m;++i){
			fa[i]=i;
		}
	}
	int find(int id){
		if(id==fa[id]) return id;
		fa[id]=find(fa[id]);
		return fa[id]; 
	}
}
vector<int>e[300001];
namespace lca{
	int fa[20][300001],deep[300001];
	void dfs(int now){
		for(int i=1;i<=19;++i){
			fa[i][now]=fa[i-1][fa[i-1][now]];
		}
		for(int i:e[now]){
			deep[i]=deep[now]+1;
			fa[0][i]=now;
			dfs(i);
		}
	}
	inline void prework(){
		for( int i=1;i<=19;++i){
			for( int j=1;j<=n+m;++j){
				fa[i][j]=fa[i-1][fa[i-1][j]];
			}
		}
	}
	inline int lca(int x,int y){
		if(deep[x]<deep[y]) swap(x,y);
		for( int i=19;i>=0;--i){
			if((deep[x]-deep[y])>=(1ll<<i)){
				x=fa[i][x];
			}
		}
		if(x==y) return x;
		for( int i=19;i>=0;--i){
			if(fa[i][x]!=fa[i][y]){
				x=fa[i][x];y=fa[i][y];
			}
		}
		return x==y?x:fa[0][x];
	}
}
namespace stree{
	#define tol (id<<1)
	#define tor (id<<1|1)
	#define mid(x,y) mid=(((x)+(y))>>1)
	struct tree{
		int w;
	}t[400001];
	inline void build(int id,int l,int r){
		if(l==r){
			t[id].w=l;
			return;
		}
		int mid(l,r);
		build(tol,l,mid);
		build(tor,mid+1,r);
		t[id].w=lca::lca(t[tol].w,t[tor].w);
	}
	inline int ask(int id,int l,int r,int L,int R){
		if(L==l and R==r) return t[id].w;
		int mid(L,R);
		if(r<=mid) return ask(tol,l,r,L,mid);
		if(l>=mid+1) return ask(tor,l,r,mid+1,R);
		return lca::lca(ask(tol,l,mid,L,mid),ask(tor,mid+1,r,mid+1,R));
	}
}
namespace hdk{
    namespace fastio{
		void rule(bool setting=false){std::ios::sync_with_stdio(setting);}
		inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
	    inline int read(int &A){A=read();return A;}
        inline char read(char &A){A=getchar();return A;}
		inline void write(int A){if(A<0){putchar('-');A=-A;}if(A>9){write(A/10);}putchar(A%10+'0');}
        inline void write(long long A){if(A<0){putchar('-');A=-A;}if(A>9){write(A/10);}putchar(A%10+'0');}
		inline void write(char A){putchar(A);}
		inline void space(){putchar(' ');}
		inline void endl(){putchar('\n');}
		#define w(a) write(a)
		#define we(a) write(a);endl()
		#define ws(a) write(a);space()
	}
}
using namespace hdk::fastio;
int main(){
//	freopen("lagrange1.in","r",stdin);
//	freopen("hdk.out","w",stdout);
	read(n);read(m);read(q);
	tot=0;
	dsu::clear();
	for( int i=1;i<=m;++i){
		int x,y;read(x);read(y);
		int fx=dsu::find(x),fy=dsu::find(y);
		if(fx==fy) continue;
		e[i+n].push_back(fx);
		e[i+n].push_back(fy);
		dsu::fa[fx]=dsu::fa[fy]=i+n;
		tot=i+n;
	}
	lca::deep[tot]=1;
	lca::dfs(tot);
//	lca::prework();
	stree::build(1,1,n);
	for( int i=1;i<=q;++i){
		int l,r;l=read();r=read();
		if(l^r){
			we(stree::ask(1,l,r,1,n)-n);
		}
		else{
			putchar('0');putchar('\n');
		}
	}
}

[25] Little Busters !

思路很简单,先对 Lun 边找边双,非边双的 Lun 是需要删除的,然后把跨越边双的 Qie 加入,并且任意两个 Qie 之间不能形成回路,可以并查集维护,最后再跑连通性即可

但是没调出来,放一下这道题的待调代码,目前的问题可能是边双找错了,导致 \(n\) 很小(也可能只是针对 Lun 是链状结构)时候答案会错

源代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
void read(char&x){
	x=' ';while(x!='L' and x!='Q') x=getchar();
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
int n,m;
struct edge{
	int to;
	bool iscutline;
	bool exist;
};
vector<edge>lun[500001];
vector<int>qie[500001];
int dfn[500001],low[500001],cnt=0;
bool iscut[500001];
bool vis[500001];
int belong[500001];
void tarjan(int now,int root){
//	cout<<"tarjan "<<now<<" "<<root<<endl;
	low[now]=dfn[now]=++cnt;
	for(edge &i:lun[now]){
		if(!dfn[i.to]){
			tarjan(i.to,now);
			low[now]=min(low[now],low[i.to]);
			if(low[i.to]>dfn[now]){
//				cout<<"iscutlun "<<now<<" "<<i.to<<endl;
//				vis[i.to]=vis[now]=true;
				i.iscutline=true;
			}
		}
		else if(dfn[i.to]<dfn[now] and i.to!=root){
			low[now]=min(low[now],dfn[i.to]);
		}
	}
}
int blcnt=0;
void dfs(int now){
//	cout<<"dfs "<<now<<endl;
	vis[now]=true;
	belong[now]=blcnt;
	for(edge &i:lun[now]){
		if(!vis[i.to] and !i.iscutline){
			dfs(i.to);
		}
	}
}
int fa[500001];
void clear(){
	for(int i=1;i<=n;++i){
		fa[i]=i;
	}
}
int find(int id){
	if(id==fa[id]) return id;
	fa[id]=find(fa[id]);
	return fa[id];
}
int tot=0;
void dfs2(int now){
	vis[now]=true;
	for(edge &i:lun[now]){
		if(vis[i.to]==true or i.exist==false) continue;
		dfs2(i.to);
	}
}
int main(){
	freopen("test.in","r",stdin);
	freopen("out.out","w",stdout);
	read(n,m);
//	cout<<n<<m<<endl;
	for(int i=1;i<=m;++i){
		int x,y;char z;
		read(x,y,z);
//		cout<<x<<y<<z<<endl;
//		cout<<z<<endl;
		if(z=='L'){
			lun[x].push_back({y,false,false});
			lun[y].push_back({x,false,false});
		}
		else{
			qie[x].push_back(y);
			qie[y].push_back(x);
		}
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i]){
			tarjan(i,0);
		}
	}
	for(int i=1;i<=n;++i){
		if(!vis[i]){
			blcnt++;
			dfs(i);
		}
	}
//	for(int i=1;i<=n;++i){
////		cout<<"????? "<<i<<endl;
//		for(edge &j:lun[i]){
//			cout<<"??? "<<i<<" "<<j.to<<" "<<j.isld<<" "<<j.iscutline<<endl;
//			if(j.isld==false and j.iscutline==false){
//				cout<<"cutlun "<<i<<" "<<j.to<<endl;
//			}
//		}
//	}
	for(int i=1;i<=n;++i){
		for(edge &j:lun[i]){
//			cout<<"bel "<<i<<" "<<j.to<<" "<<belong[i]<<"?="<<belong[j.to]<<endl;
			if(belong[i]==belong[j.to]){
				j.exist=true;
				tot++;
//				cout<<"remain "<<i<<" "<<j.to<<endl;
			}
		}
	}
	clear();
	for(int i=1;i<=n;++i){
		for(int &j:qie[i]){
			int f_a=find(belong[i]),f_b=find(belong[j]);
			if(f_a!=f_b){
				fa[f_a]=f_b;
				lun[i].push_back({j,true,true});
				lun[j].push_back({i,true,true});
				tot+=2;
			}
		}
	}
	memset(vis,0,sizeof vis);
	dfs2(1);
	for(int i=1;i<=n;++i){
		if(!vis[i]){
			cout<<"NO"<<endl;
			return 0;
		}
	}
	cout<<"YES"<<endl;
	cout<<tot/2<<endl;
	for(int i=1;i<=n;++i){
		for(edge &j:lun[i]){
			if(i<j.to and j.exist){
				cout<<i<<" "<<j.to<<endl;
			}
		}
	}
}
对拍用正解代码

感谢 PeppaEvenPig

#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
using namespace std;
int n, m;
struct sss{
	int f, t, ne, w;
}e[500005];
int h[500005], cnt;
void add(int u, int v, int ww) {
	e[++cnt].t = v;
	e[cnt].f = u;
	e[cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].w = ww;
}
int dfn[500005], low[500005];
bool vis[500005], bri[500005], vi[500005];
int belog[500005], ecc;
int dcnt;
vector<int> v[500005];
int fa[500005];
int find(int x) {
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
void Tarjan(int x, int fa) {
	dfn[x] = low[x] = ++dcnt;
	for (int i = h[x]; i; i = e[i].ne) {
		if (e[i].w == 2) continue;
		int u = e[i].t;
		if (u == fa) continue;
		if (!dfn[u]) {
			Tarjan(u, x);
			low[x] = min(low[x], low[u]);
			if (low[u] > dfn[x]) {
				if (i & 1) {
					bri[i] = bri[i + 1] = true;
				} else {
					bri[i] = bri[i - 1] = true;
				}
			}
		} else {
			low[x] = min(low[x], dfn[u]);
		}
	}
}
void dfs(int x) {
	belog[x] = ecc;
	vis[x] = true;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (e[i].w == 2 || vis[u] || bri[i]) continue;
		dfs(u);
	}
}
void ddfs(int x) {
	vis[x] = true;
	for (int i = 0; i < v[x].size(); i++) {
		int u = v[x][i];
		if (vis[u]) continue;
		ddfs(u);
	}
}
int main() {
	freopen("test.in","r",stdin);
	freopen("ans.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= m; i++) vi[i] = false;
	int x, y;
	string s;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		cin >> s;
		if (s == "Lun") {
			add(x, y, 1);
			add(y, x, 1);
		}
		if (s == "Qie") {
			add(x, y, 2);
			add(y, x, 2);
		}
	}
	for (int i = 1; i <= n; i++) {
		if (!dfn[i]) Tarjan(i, 0);
	}
	for (int i = 1; i <= n; i++) {
		if (!belog[i]) {
			ecc++;
			dfs(i);
		}
	}
	for (int i = 1; i <= m; i++) {
		if (belog[e[i * 2].f] == belog[e[i * 2].t] && e[i * 2].w == 1) {
			vi[i] = true;
		}
	}
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		int aa = find(belog[e[i * 2].f]);
		int bb = find(belog[e[i * 2].t]);
		if (aa != bb && e[i * 2].w == 2) {
			fa[aa] = bb;
			vi[i] = true;
		}
	}
	int sum = 0;
	for (int i = 1; i <= m; i++) {
		if (vi[i]) {
			sum++;
			v[e[i * 2].f].push_back(e[i * 2].t);
			v[e[i * 2].t].push_back(e[i * 2].f);
		}
	}
	memset(vis, 0, sizeof(vis));
	ddfs(1);
	for (int i = 1; i <= n; i++) {
		if (!vis[i]) {
			cout << "NO";
			return 0;
		}
	}
	cout << "YES" << '\n';
	cout << sum << '\n';
	for (int i = 1; i <= m; i++) {
		if (vi[i]) {
			cout << e[i * 2].f << ' ' << e[i * 2].t << '\n';
		}
	}
	return 0;
}
数据生成代码

由于只是小数据会出问题,所以造的是 \(n\) 很小时候的满无向图

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#include<hdk/rand.h>
int r=rander::reset();
mt19937 rd(rand());
int main(){
	freopen("test.in","w",stdout);
	map<pair<int,int>,bool>mp;
	int n=3,m=n*(n-1)/2;
	cout<<n<<" "<<m<<endl;
	for(int i=2;i<=n;++i){
		int res=rd()%(i-1)+1;
		cout<<res<<" "<<i<<" "<<(rd()%2?"Lun":"Qie")<<endl;
		mp[{res,i}]=true;
	}
	for(int i=1;i<=m-n+1;++i){
		int x=rand()%n+1,y=rand()%n+1;
		if(x>y) swap(x,y);
		while(x==y or mp.count({x,y})){
			x=rand()%n+1,y=rand()%n+1;
			if(x>y) swap(x,y);
		}
		cout<<x<<" "<<y<<" "<<(rd()%2?"Lun":"Qie")<<endl;
		mp[{x,y}]=true;
	}
}


Special Judge

编译需要 Testlib

#include "testlib.h"

using namespace std;

const int max1 = 2e5;

int n, m, k;
map < pair <int, int>, char > Map;
vector <int> edge[max1 + 5];
set < pair <int, int> > out;

int dfn[max1 + 5], low[max1 + 5], dfs_clock;
int s[max1 + 5], top;
int belong[max1 + 5], cnt;

void Tarjan ( int now, int fa )
{
    dfn[now] = low[now] = ++dfs_clock;
    s[++top] = now;

    for ( auto v : edge[now] )
    {
        if ( v == fa )
            continue;
        
        if ( !dfn[v] )
        {
            Tarjan(v, now);
            low[now] = min(low[now], low[v]);
        }
        else
            low[now] = min(low[now], dfn[v]);
    }

    if ( dfn[now] == low[now] )
    {
        int x; ++cnt;

        do
        {
            x = s[top--];
            belong[x] = cnt;
        } while ( x != now );
    }
    return;
}

int main ( int argc, char* argv[] )
{
    registerTestlibCmd(argc, argv);
    int u, v; string s;
    n = inf.readInt(), m = inf.readInt();

    for ( int i = 1; i <= m; i ++ )
    {
        u = inf.readInt();
        v = inf.readInt();
        s = inf.readString();

        Map[make_pair(u, v)] = s[1];
        Map[make_pair(v, u)] = s[1];

        // putchar(s[1]);
        // putchar('\n');
    }

    string s1 = ouf.readString(), s2 = ans.readString();

    if ( s1 != s2 )
        quitf(_wa, "Your answer is wrong!!!");
    
    if ( s1 == "NO" )
        quitf(_ok, "Good!!!");
    
    k = ouf.readInt();

    if ( k > m )
        quitf(_wa, "Your edges are illegal!!!");

    for ( int i = 1; i <= k; i ++ )
    {
        u = ouf.readInt();
        v = ouf.readInt();

        if ( Map.find(make_pair(u, v)) == Map.end() )
            quitf(_wa, "Your edges are illegal!!!");
        if ( out.find(make_pair(u, v)) != out.end() )
            quitf(_wa, "Your edges are illegal!!!");

        edge[u].push_back(v);
        edge[v].push_back(u);

        out.insert(make_pair(u, v));
        out.insert(make_pair(v, u));
    }

    Tarjan(1, 0);
    for ( int i = 1; i <= n; i ++ )
        if ( !dfn[i] )
            quitf(_wa, "Your graph is not connected!!!");
    
    for ( auto v : out )
    {
        if ( Map[v] == 'L' )
        {
            if ( belong[v.first] != belong[v.second] )
                quitf(_wa, "The Lun edge does not belong to any circles!!!");
        }
        else
        {
            if ( belong[v.first] == belong[v.second] )
                quitf(_wa, "The Qie edge belongs to a circle!!!");
        }
    }

    quitf(_ok, "Good!!!");

    return 0;
}
对拍代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
int cnt=0;
int main(){
	while(1){
		cnt++;
		cout<<"test "<<cnt<<endl;
		system("fds.exe");
		cout<<"Create Finished "<<endl;
		system("grqgh.exe");
		cout<<"Test Finished "<<endl;
		system("未命名13.exe");
		cout<<"Answer Create Finished "<<endl;
		if(system("spj.exe test.in out.out ans.out")) system("pause");
	}
}


有机会再来调吧

[3] 简单的线段树题

这个题涉及到了重构树求最值的问题,写了比较有启发意义的爆改线段树

此题可以将时间戳设计成点权,考虑到重构树的特殊性质,我们可以将其按时间戳全部联通,根据任意两点路径边权最大值为重构树上LCA的点权,此题我们需要求的正好就是边权最大值,因此直接维护树上 LCA 即可. 取最值可以用 st 表或线段树.

这里没排序是因为时间戳是有序的,用的线段树来维护,更新子节点节点最大权值的时候也可以直接 LCA 解决,比较方便,只是常数比 ST 表大,稍微卡一下能过.

点击查看代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1000001;
int a[N];
namespace stree{
	#define tol (id*2)
	#define tor (id*2+1)
	#define mid(l,r) mid=((l)+(r))/2
	struct tree{
		int l,r;
		int sum;
		bool tag=false;
	}t[N*4];
	inline void build(int id,int l,int r){
		t[id].l=l;
		t[id].r=r;
		if(l==r){
			t[id].sum=a[l];
			return;
		}
		int mid(l,r);
		build(tol,l,mid);
		build(tor,mid+1,r);
		t[id].sum=t[tol].sum+t[tor].sum;
	}
	inline int ask(int id,int l,int r){
		if(l<=t[id].l and t[id].r<=r){
			return t[id].sum;
		}
		if(r<=t[tol].r){
			return ask(tol,l,r);
		}
		if(l>=t[tor].l){
			return ask(tor,l,r);
		}
		int mid(t[id].l,t[id].r);
		return ask(tol,l,mid)+ask(tor,mid+1,r);
	}
	inline void change(int id,int l,int r){
		if(t[id].tag or !t[id].sum) return;
		if(t[id].l==t[id].r){
			t[id].sum=floor(sqrt(t[id].sum+0.1));
			if(t[id].sum==1) t[id].tag=true;
			return;
		}
		if(r<=t[tol].r){
			change(tol,l,r);
		}
		else if(l>=t[tor].l){
			change(tor,l,r);
		}
		else{
			int mid(t[id].l,t[id].r);
			change(tol,l,mid);
			change(tor,mid+1,r);
		}
		t[id].sum=t[tol].sum+t[tor].sum;
		t[id].tag=(t[tol].tag and t[tor].tag);
	}
}
namespace hdk{
	namespace fastio{
		void rule(bool setting=false){std::ios::sync_with_stdio(setting);}
		inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
	    inline int read(int &A){A=read();return A;}
        inline char read(char &A){A=getchar();return A;}
        inline void write(long long A){if(A<0){putchar('-');A=-A;}if(A>9){write(A/10);}putchar(A%10+'0');}
		inline void write(char A){putchar(A);}
		inline void space(){putchar(' ');}
		inline void endl(){putchar('\n');}
		#define w(a) write(a)
		#define we(a) write(a);endl()
		#define ws(a) write(a);space()
	}
}
using namespace stree;
using namespace hdk::fastio;
signed main(){
	int n;
	n=read();
	for(int i=1;i<=n;++i){
		a[i]=read();
	}
	build(1,1,n);
	int m;
	m=read();
	for( int i=1;i<=m;++i){
		int op=read(),l=read(),r=read();
		if(l>r) continue;
		if(op==0){
			change(1,l,r);
		}
		else{
			we(ask(1,l,r));
		}
	}
}

[6] 回收波特

Arc149D

注意到值域有限,考虑对值域内所有数处理出答案

观察一次操作过后,一部分超过原点,一部分到达原点,一部分没过原点,

到达的不用管而超过的部分,后续操作和关于原点对称的位置的操作一模一样,于是可以合并

每次将正半轴负半轴较短的一截与另一边合并起来

这样每个点只会操作 \(O(1)\) 次,复杂度就可以保证了

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
int n,m;
int a[300001],D[300001];
int fa[1000001],val[1000001];
int find(int x){
	if(fa[x]==x) return x;
	int dad=fa[x];
	fa[x]=find(fa[x]);
	val[x]^=val[dad];
	return fa[x];
}
void clear(int l,int r){
	for(int i=l;i<=r;i++){
		fa[i]=i;
	}
} 
int ans[1000001];
signed main(){
	read(n,m);
	for(int i=1;i<=n;i++){
		read(a[i]);
	}
	for(int i=1;i<=m;i++){
		read(D[i]);
	}
	int l=a[1],r=a[n];
	int L=a[1],R=a[n];
	clear(L,R);
	for(int i=1;i<=m;i++){
		if(l>0){
			l-=D[i],r-=D[i];
		}
		else{
			l+=D[i],r+=D[i];
		}
		if(l<=0 and 0<=r){
			int x=R-r,fx=find(x);
			ans[fx]=i;
			if(-l<r){
				for(int i=1;i<=-l;i++){
					fa[x-i]=x+i;
					val[x-i]^=1;
				}
				L=x+1;
				l=1;
			}
			else{
				for(int i=1;i<=r;i++){
					fa[x+i]=x-i;
					val[x+i]^=1;
				}
				R=x-1;
				r=-1;
			} 
		}
	} 
	int now;
	for(int i=1;i<=n;i++){
		int x=find(a[i]);
		if(ans[x]){
			printf("Yes %lld\n",ans[x]);
		}
		else{
			now=r-R+x;
			if(val[a[i]]){
				now*=-1;
			}
			printf("No %lld\n",now);
		}
	}
}

posted @ 2024-09-13 11:50  HaneDaniko  阅读(46)  评论(2编辑  收藏  举报