补题记录
Todo List (\(15/40\))
已抛弃
- [7] 满穗
可能做
- [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\) 在最小生成树上路径中边权最大值,容易发现答案即为
如果我们可以求得 \(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);
}
}
}