省选模拟赛

又双叒开始记这个东西了,最好别又记一半不计了。

省选模拟4

15+4+60

A.我醉

这个 [0,1000] 的值域很不对劲,想了很久,不出意外也是和正解没有关系。打了个70的暴力还挂了。我醉。

考虑分讨串的奇偶性并二分答案,对于当前待验证的长度 L 用点分治+hash处理,具体地对于已经处理过的枝杈用一个什么东西存一下已有的hash值和深度二元组 (H,dep),新引出来的枝杈就去查是否存在一个 (H,Ldep) 使得两段hash能拼一个回文串,复杂度 O(nlog2n)。话是这么说的实现却比较蛋疼。就比如说这个判断回文就比较有说法因为 depLdep 分别对应短链和长链所以先在长链里按 L 之类找一个对称中心看一下满不满足子串是回文串,是了之后再用短链取匹配剩下的部分。

然后卡常啊很烦注意到不刻意构造的情况下答案很小所以答案上界削一下就能过了要不然跟暴力坐一桌。

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define MAXN 100005
#define ull unsigned long long
#define pii pair<ull,int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
const ull p=13331;
ull pw[MAXN],has1[MAXN],has2[MAXN];
int n;
set<pii>sav,stac;
struct node{
	int v,w,nxt;
}edge[MAXN<<1];
int h[MAXN],tmp;
inline void add(int u,int v,int w){
	edge[++tmp]=(node){v,w,h[u]};
	h[u]=tmp;
}
int ans=1;
bool vis[MAXN],F;
int lim,typ,L;
int siz[MAXN],dp[MAXN],root,sum;
inline void getrt(int u,int fa){
	if(F)return;
	dp[u]=0;
	siz[u]=1;
	for(int i=h[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(vis[v]||v==fa)continue;
		getrt(v,u);
		siz[u]+=siz[v];
		dp[u]=max(dp[u],siz[v]);
	}
	dp[u]=max(dp[u],sum-siz[u]);
	if(dp[u]<dp[root])root=u;
}
inline void getdis(int u,int fa,int dep){
	if(dep<=lim){
		stac.insert(mp(has1[dep],dep));
		pii tar=mp(has1[dep],L-dep);
		if(*(sav.lower_bound(tar))==tar){
			F=1;
			return ;
		}
	}
	else{
		if(L&1){
			ull Hpp=has1[dep-lim]*pw[dep-lim-1],Hps=has2[2*(dep-lim)-1]-has2[dep-lim-1];
			if(Hpp==Hps){
				stac.insert(mp(has1[dep]-has1[2*(dep-lim)-1]*pw[2*lim-dep+1],dep));
				pii tar=mp(has1[dep]-has1[2*(dep-lim)-1]*pw[2*lim-dep+1],L-dep);
				if(*(sav.lower_bound(tar))==tar){
					F=1;
					return ;
				}
			}
		}
		else{
			ull Hpp=has1[dep-lim]*pw[dep-lim],Hps=has2[2*(dep-lim)]-has2[dep-lim];
			if(Hpp==Hps){
				stac.insert(mp(has1[dep]-has1[2*(dep-lim)]*pw[2*lim-dep],dep));
				pii tar=mp(has1[dep]-has1[2*(dep-lim)]*pw[2*lim-dep],L-dep);
				if(*(sav.lower_bound(tar))==tar){
					F=1;
					return ;
				}
			}
		}
	}
	for(int i=h[u];i;i=edge[i].nxt){
		int v=edge[i].v,w=edge[i].w;
		if(v==fa||vis[v])continue;
		has1[dep+1]=has1[dep]*p+w;
		has2[dep+1]=has2[dep]+w*pw[dep];
		getdis(v,u,dep+1);	
	}
}
inline void work(int u){
	if(F)return ;
	sav.clear();
	sav.insert(mp(0,0));
	for(int i=h[u];i;i=edge[i].nxt){
		int v=edge[i].v,w=edge[i].w;
		if(vis[v])continue;
		has1[1]=has2[1]=w;
		stac.clear();
		getdis(v,u,1);
		for(set<pii>::iterator it=stac.begin();it!=stac.end();it++)sav.insert(*it);
	}
} 
inline void solve(int u){
	if(F)return ;
	work(u);
	vis[u]=1;
	for(int i=h[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(vis[v])continue;
		sum=siz[v],root=0,dp[root]=n;
		getrt(v,u);
		solve(root);
	}
}
inline bool check(int tar){
	memset(vis,0,sizeof(vis));
	F=0;lim=tar/2;
	root=0,dp[root]=sum=n;
	L=tar;
	getrt(1,0);
	solve(root);
	return F;
}
signed main(){
	freopen("name.in","r",stdin);
	freopen("name.out","w",stdout);
	pw[0]=1;
	for(int i=1;i<MAXN;i++)pw[i]=pw[i-1]*p;
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	int l=1,r=1650;
	while(r>=l){
		int mid=l+r>>1;
		if(check(mid*2))ans=mid*2,l=mid+1;
		else r=mid-1;
	}
	l=1,r=1800;
	while(r>=l){
		int mid=l+r>>1;
		if(check(mid*2-1))ans=max(ans,mid*2-1),l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}

B.梧桐依旧

转化题意为求 NN 的矩阵 A 在满秩矩阵 B× 构成的置换群中的不动点个数。转化失败遗憾离场说是。

把 Burnside 引理转化一下

gG|Xg|=|G||X/G|

其中 |G| 就是满秩矩阵 B 的个数,因为从一开始到 i 填充的向量要保证线性无关统计一下应该是

|G|=i=0n1pnpi

好玩的是考场上观察出来答案总包含这么个形式,然后弃了。

然后求一下轨道即等价类个数,钦定秩的个数为 x 的时候求 B×A 的不同个数,这个可以枚举一下然后算

|X/G|=i=0nj=0i1pnpjpipj

然后这个是 O(n2),你反着跑一遍分母可以 O(n) 跑出来,复杂度就是 O(n)

C.卿且去

白送60pts还是比较友善的。

60pts也要用的一个结论:显然取 (n2,n] 是最优的,然后观察数据范围和设问应该是一个亚线性筛子题。

然后容易得到答案式子可以表示为

i=n2+1nj=1d(i)(1)j1(ij)2π(n)j

=i=n2+1n2π(n)j=1d(i)(1)j1(ij)2j

=i=n2+1n2π(n)2d(i)12d(i)

=(nn2)2π(n)2π(n)i=n2+1n2d(i)

π(n) 可以 min25筛
,后面那一坨也可以 min25。

#include<bits/stdc++.h>
#define int long long
#define MAXN 200005
#define idx(x) ((x)<=V?id[0][(x)]:id[1][n/(x)])
using namespace std;
const int mod=998244353,inv2=499122177;
int n;
bool vis[MAXN];
int prime[MAXN],tot;
int id[2][MAXN],V,w[MAXN],cnt;
int g[MAXN];
inline void INIT(int n){
	vis[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i])prime[++tot]=i;
		for(int j=1;j<=tot&&i*prime[j]<=n;j++){
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
inline int qpow(int base,int power){
	int res=1;
	while(power){
		if(power&1)res=res*base%mod;
		base=base*base%mod;
		power>>=1;
	}
	return res%mod;
}
inline int S(int x,int i){
	if(x<2||prime[i]>=x)return 0;
	int res=(g[idx(x)]-i*inv2%mod+mod)%mod;
	for(int j=i+1;j<=tot&&prime[j]*prime[j]<=x;j++){
		for(int k=1,p=prime[j];p<=x;p*=prime[j],k++){
			res=(res+(S(x/p,j)+(k>1?1:0))*inv2%mod+mod)%mod;
		}
	}
	return res%mod;
}
int Pi,val,ans;
signed main(){
	freopen("yyds.in","r",stdin);
	freopen("yyds.out","w",stdout);
	scanf("%lld",&n);	
	V=sqrt(n);
	for(int l=1,r=0;l<=n;l=r+1){
		r=n/(n/l);
		w[++cnt]=n/l;
		if(w[cnt]<=V)id[0][w[cnt]]=cnt;
		else id[1][n/w[cnt]]=cnt;
	}
	INIT(V);
	for(int i=1;i<=cnt;i++)g[i]=(w[i]-1)%mod;
	for(int j=1;j<=tot;j++){
		for(int i=1;i<=cnt&&prime[j]*prime[j]<=w[i];i++)
			g[i]=(g[i]-g[idx(w[i]/prime[j])]+j-1+mod)%mod;
	}
	Pi=g[1];
	for(int i=1;i<=cnt;i++)g[i]=g[i]*inv2%mod;
	val=(S(n,0)-S(n>>1,0)+mod)%mod;
	ans=qpow(2,Pi)*((n-(n>>1))-val+mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

省选模拟5

10+30+100

A.蛋糕

然蛋题面。

考虑转化方案为这样的构造:对一条直线横向地连到它相邻的点上,然后顺时针或者逆时针旋转且不切到其他点,这一部分答案应为 4n(n1),然后考虑两点之间不单为横/纵的情况,设两个偏移为 (i,j),有汇总答案:

Ans=4n(n1)+2i=1n(ni)j=1n(nj)[gcd(i,j)=1]

后面一部分就莫反一下,推出来之后可以给前面的汇总成

4i=1n(n1)(nϕ(i)iϕ(i)2)

=4n2i=1nϕ(i)6ni=1niϕ(i)+2i=1]ni2ϕ(i)

这三个长得很筛子,前两个分别分配一个 1,id 狄卷,是板子了,第三个可以参考这道题给一个 (id)2 卷完杜教筛是一个

g(1)S(n)=i=1n(fg)(i)i=2ng(i)S(ni)=i=1ni3i=1ni2S(ni)

的形式,就可以做了。

B.巧克力

送30还是比较香的。

30pts可以用维护lst跑,先转成tj里的nxt然后答案式子是

i=lrmin{min(nxtj)(ijr),r+1}i

不可持久化的话,就拿set维护一下nxt的修改,对于答案计算就用线段树维护全区间答案和最小的next以及它所属的下标,信息合并可以线段树二分一个左边最靠右的比右边min小的点然后合成新的右边的min,重算一下答案,单次复杂度 O(logn)。询问就从左到右合并区间求答案,总复杂度是 O(nlog2n) 的。

然后加一下可持久化,相当于是把原先存nxt的set和其他数组换成主席树,时间复杂度不变,空间卡一卡。

话是这么说的但是实现的细节挺恶心的,先摆个码。

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define ll long long
#define MAXN 100005
using namespace std;
int n,q;
int a[MAXN],loc[MAXN],nxt[MAXN];
namespace Segment_Tree_ValIndex{
	#define ls(p) tree[p].lson
	#define rs(p) tree[p].rson
	#define Ls(p) Tree[p].lson
	#define Rs(p) Tree[p].rson
	struct TREE{
		int lson,rson;
		int val;
	}tree[(MAXN<<2)*18],Tree[(MAXN<<2)*18];
	int rt[MAXN],tot;
	inline void modify(int l,int r,int x,int k,int &p,int pre){
		p=++tot;
		tree[p]=tree[pre];
		tree[p].val+=k;
		if(l==r)return ;
		int mid=l+r>>1;
		if(x<=mid)modify(l,mid,x,k,ls(p),ls(pre));
		else modify(mid+1,r,x,k,rs(p),rs(pre));
	}
	inline int lgetloc(int l,int r,int x,int p){
		if(!tree[p].val)return 0;
		if(l==r)return l;
		int mid=l+r>>1;
		int res=0;
		if(x<=mid)return lgetloc(l,mid,x,ls(p));
		res=lgetloc(mid+1,r,x,rs(p));
		if(res)return res;
		return lgetloc(l,mid,mid,ls(p));
	}
	inline int rgetloc(int l,int r,int x,int p){
		if(!tree[p].val)return n+1;
		if(l==r)return l;
		int mid=l+r>>1;
		int res=0;
		if(x>mid)return rgetloc(mid+1,r,x,rs(p));
		res=rgetloc(l,mid,x,ls(p));
		if(res!=n+1)return res;
		return rgetloc(mid+1,r,mid+1,rs(p));
	}
	int cnt,Rt[MAXN<<1];
	inline void build(int l,int r,int &p){
		p=++cnt;
		if(l==r){
			Tree[p].val=rt[l];
			return;
		}
		int mid=l+r>>1;
		build(l,mid,Ls(p));
		build(mid+1,r,Rs(p));
	}	
	inline void Modify(int l,int r,int x,int k,int &p,int pre){
		p=++cnt;
		Tree[p]=Tree[pre];
		if(l==r){
			Tree[p].val=k;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid)Modify(l,mid,x,k,Ls(p),Ls(pre));
		else Modify(mid+1,r,x,k,Rs(p),Rs(pre));
	}
	inline int query(int l,int r,int x,int p){
		if(l==r)return Tree[p].val;
		int mid=l+r>>1;
		if(x<=mid)return query(l,mid,x,Ls(p));
		else return query(mid+1,r,x,Rs(p));
	}
	#undef ls(p)
	#undef rs(p)
	#undef Ls(p)
	#undef Rs(p)
}
using Segment_Tree_ValIndex::Rt;
using Segment_Tree_ValIndex::rt;
using Segment_Tree_ValIndex::cnt;
using Segment_Tree_ValIndex::tot;
struct Segment_Tree{
	#define ls(p) tree[p].lson
	#define rs(p) tree[p].rson
	struct TREE{
		int lson,rson;
		ll val,sum;
		int nval,loc;
	}tree[(MAXN<<2)*18];
	int tot,rt[MAXN<<1];
	inline int ask(int l,int r,int x,ll &y,int p){
		if(l==r)return l;
		int mid=l+r>>1;
		if(tree[rs(p)].nval<x)return ask(mid+1,r,x,y,rs(p));
		y+=tree[rs(p)].sum+tree[p].val;
		return ask(l,mid,x,y,ls(p));
	}
	inline void push_up(int l,int r,int ls,int rs,int p){
		if(tree[rs].nval<=tree[ls].nval){
			tree[p].nval=tree[rs].nval;
			tree[p].loc=tree[rs].loc;
			tree[p].sum=tree[rs].sum;
			return ;
		}
		tree[p].val=0;
		tree[p].sum=tree[ls].sum+tree[rs].sum+(tree[p].val=(ll)tree[rs].nval*(tree[rs].loc-ask(l,r,tree[rs].nval,tree[p].val,ls))-tree[p].val);
		tree[p].nval=tree[ls].nval;
		tree[p].loc=tree[ls].loc;
	}
	inline void build(int l,int r,int &p){
		p=++tot;
		if(l==r){
			tree[p].val=a[l];
			tree[p].nval=nxt[l];
			tree[p].loc=l;
			return ;
		}
		int mid=l+r>>1;
		build(l,mid,ls(p));
		build(mid+1,r,rs(p));
		push_up(l,mid,ls(p),rs(p),p);
	}
	inline void modify(int l,int r,int x,int d,int y,int &p,int pre){
		p=++tot;
		ls(p)=ls(pre),rs(p)=rs(pre);
		if(l==r){
			tree[p].nval=d;
			tree[p].loc=l;
			tree[p].val=y;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid)modify(l,mid,x,d,y,ls(p),ls(pre));
		else modify(mid+1,r,x,d,y,rs(p),rs(pre));
		push_up(l,mid,ls(p),rs(p),p);
	}
	inline int query(int l,int r,int x,int p){
		if(l==r)return p;
		int mid=l+r>>1;
		if(x<=mid)return query(l,mid,x,ls(p));
		else return query(mid+1,r,x,rs(p));
	}
	inline void change(int l,int r,int ul,int ur,int p){
		if(l>=ul&&r<=ur){
			push_up(l,r,p,tot+1,tot+1);
			return ;
		}
		int mid=l+r>>1;
		if(ur>mid)change(mid+1,r,ul,ur,rs(p));
		if(ul<=mid)change(l,mid,ul,ur,ls(p));
	}
}ST;
#define loc(p) ST.tree[p].loc
#define nval(p) ST.tree[p].nval
#define sum(p) ST.tree[p].sum
#define val(p) ST.tree[p].val 
int T;
ll lans;
signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),loc[i]=n+1;
	for(int i=n;i>=1;i--){
		nxt[i]=loc[a[i]];
		loc[a[i]]=i;
		Segment_Tree_ValIndex::modify(1,n,i,1,rt[a[i]],rt[a[i]]);
	}
	ST.build(1,n,ST.rt[0]);
	Segment_Tree_ValIndex::build(1,n,Rt[0]);
	scanf("%d",&q);
	for(int i=1,opt,x,l,r;i<=q;i++){
		scanf("%d%d%d%d",&opt,&x,&l,&r);
		l=(l+lans)%n+1;
		r=(r+lans)%n+1;
		if(opt==1){
			++T;
			ST.rt[T]=ST.rt[x];
			Rt[T]=Rt[x];
			int loc=ST.query(1,n,l,ST.rt[T]);
			int p=Segment_Tree_ValIndex::query(1,n,val(loc),Rt[T]);
			if(val(loc)==r)continue;
			Segment_Tree_ValIndex::modify(1,n,l,-1,p,p);
			Segment_Tree_ValIndex::Modify(1,n,val(loc),p,Rt[T],Rt[T]);
			p=Segment_Tree_ValIndex::lgetloc(1,n,l,p);
			if(p)ST.modify(1,n,p,nval(loc),val(loc),ST.rt[T],ST.rt[T]);
			p=Segment_Tree_ValIndex::query(1,n,r,Rt[x]);
			ST.modify(1,n,l,Segment_Tree_ValIndex::rgetloc(1,n,l,p),r,ST.rt[T],ST.rt[T]);
			int tar=Segment_Tree_ValIndex::lgetloc(1,n,l,p);
			if(tar)ST.modify(1,n,tar,l,r,ST.rt[T],ST.rt[T]);
			Segment_Tree_ValIndex::modify(1,n,l,1,p,p);
			Segment_Tree_ValIndex::Modify(1,n,r,p,Rt[T],Rt[T]); 
		}
		else{
			nval(ST.tot+1)=r+1;
			sum(ST.tot+1)=0;
			loc(ST.tot+1)=r;
			ST.change(1,n,l,r,ST.rt[x]);
			printf("%lld\n",lans=sum(ST.tot+1)+(ll)nval(ST.tot+1)*(loc(ST.tot+1)-l+1)-(ll)(l+r)*(r-l+1)/2);
		}
	}
	return 0;
}

C.奶酪

喜欢简单题放T3。

抽一条直径出来。以一个直径端点为根dp子树直径,求答案的时候分讨一下。如果是非直径边肯定有一个答案就是直径,另一个可以dp。如果是直径边那有一个答案可以dp,另一个答案就把根换到另一个直径端点再dp一遍求出。

省选模拟6

100+10+25

A.鸬鹚

考虑把矩形按上端点排序,逐个插入即可实现一个踢队尾的均摊效果,然而合并是不太容易的,用线段树维护 x 坐标并分别在每个节点记录完全覆盖它的和在它之内的矩形,显然这两种元素在单个线段树节点内可以相消(只需要 y) 相交,这样的话时间复杂度是均摊 O(nlogn) 的,空间复杂度也是...吧。

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define MAXN 100005
#define M 2000005
using namespace std;
int n,top;
namespace MYYY_Fast_IO{
	inline int read(){
		int w{1},x{};
		char c=getchar();
		while(c<'0'||c>'9'){if(c == '-')w=-1;c=getchar();}
		while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
		return w * x;
	}
	inline void write(int x){
		if(x<0) x=-x,putchar('-');
		if(x>9)write(x/10);
		putchar(x%10+'0');
	}
	inline void writeln(int x){write(x);putchar(10);}
	inline void writek(int x){write(x);putchar(' ');}
}
using namespace MYYY_Fast_IO;
struct node{
    int x1,x2,y1,y2;
    bool operator<(const node &pre)const{
		if(x1!=pre.x1)return x1<pre.x1;
		if(x2!=pre.x2)return x2<pre.x2;
		if(y1!=pre.y1)return y1<pre.y1;
		return y2<pre.y2;
	}
}sq[MAXN],ans[MAXN];
bool del[MAXN];
int hp[M];
int refl[M],tot,lfer[M],cnt;
inline bool cmp(node pre,node b){
    return pre.y2<b.y2;
}
inline bool check(int x,int y){
	node pre=sq[x],b=sq[y];
    return pre.y2>b.y1&&pre.y1<b.y2&&pre.x2>b.x1&&pre.x1<b.x2;
}
inline void merge(int x,int y){
	sq[x].x1=min(sq[x].x1,sq[y].x1);
	sq[x].y1=min(sq[x].y1,sq[y].y1);
	sq[x].x2=max(sq[x].x2,sq[y].x2);
	sq[x].y2=max(sq[x].y2,sq[y].y2);
}
struct Segment_Tree{
	#define ls(p) p<<1
	#define rs(p) p<<1|1
	#define sav(p) tree[p].sav
	#define stac(p) tree[p].stac
    struct TREE{
        vector<int>sav,stac;
    }tree[MAXN<<3];
    inline void modify(int l,int r,int ul,int ur,int id,int p){
        stac(p).emplace_back(id);
        if(l>=ul&&r<=ur){
            sav(p).emplace_back(id);
            return;
        }
        int mid=l+r>>1;
        if(ul<=mid)modify(l,mid,ul,ur,id,ls(p));
        if(ur>mid)modify(mid+1,r,ul,ur,id,rs(p));
    }
    inline bool fix(int p,int id){
        int res=0;
	   	while(!stac(p).empty()){
	   		if(del[stac(p).back()])stac(p).pop_back();
	   		else if(check(stac(p).back(),id)){
	   			int u=stac(p).back();
	   			del[u]=1;
	   			merge(id,u);
	   			res=1;
	   			stac(p).pop_back();
			}
			else break;
		}
        return res;
    }
    inline bool update(int l,int r,int ul,int ur,int id,int p){
        bool f=0;
        while(!sav(p).empty()){
				if(del[sav(p).back()])sav(p).pop_back();
	            else if(check(sav(p).back(),id)){
	            	int u=sav(p).back();
	                del[u]=1;
	                merge(id,u);
	                f=1;
					sav(p).pop_back();
				}
				else break;
	    }
	    if(f)return 1;
        if(l>=ul&&r<=ur)return fix(p,id);
        int mid=l+r>>1;
        int res=0;
        if(ul<=mid)res|=update(l,mid,ul,ur,id,ls(p));
        if(ur>mid)res|=update(mid+1,r,ul,ur,id,rs(p));
        return res;
    }
}ST;
signed main(){
  //  freopen("T1i.in","r",stdin);
    //freopen("T1o.out","w",stdout);
	n=read();
    for(int i=1;i<=n;i++){
    	sq[i].x1=read();
    	sq[i].x2=read();
    	sq[i].y1=read();
    	sq[i].y2=read();
		hp[++tot]=sq[i].x1,hp[++tot]=sq[i].x2;
    }
    sort(hp+1,hp+1+tot);
    for(int i=1;i<=tot;i++){
    	if(!refl[hp[i]])refl[hp[i]]=++cnt,lfer[cnt]=hp[i];
	}
	sort(sq+1,sq+1+n,cmp);
    for(int i=1;i<=n;i++){
    	sq[i].x1=refl[sq[i].x1];
    	sq[i].x2=refl[sq[i].x2];
    	while(ST.update(1,cnt,sq[i].x1,sq[i].x2,i,1));
    	ST.modify(1,cnt,sq[i].x1,sq[i].x2,i,1);
    }
    for(int i=1;i<=n;i++){
    	if(del[i])continue;
    	ans[++top]=sq[i];
    	ans[top].x1=lfer[ans[top].x1];
    	ans[top].x2=lfer[ans[top].x2];
	}
    sort(ans+1,ans+1+top);
    writeln(top);
    for(int i=1;i<=top;i++)writek(ans[i].x1),writek(ans[i].x2),writek(ans[i].y1),writeln(ans[i].y2);
    return 0;
}
/*
5
7 8 1 4
1 5 2 3
4 5 2 7
2 3 5 9
4 6 8 9
*/

B.雪雀

就是问你一个 3×N 的矩阵的全点对最短路和,这个 3 显然是有说法的,可以发现最短路一定仅会穿过一个 x=x0 至多两次,因为三次就是S型了会相邻,一定不优,两次的话相当于 x=x0 的一侧是一个U型。

考虑分治解决这个问题,每次从 n 这条边劈开一个 x=x0,然后处理左右两侧的最短路和,比如现在整从左到右的,线左右侧六个点从上到下是 L1,R1,L2,R2,L3,R3,根据上面的结论在这条线上只会存在 LiRi 的路径,然后就要求他们仨分别负责了哪些路径的最短路,这个你直接写个最短路就行了。

后面忘了,嘻嘻。

C.燕鸥

这个是打表找规律题。

假设不进行钦定发现序列会以一个 ai=3(ai1+1) 的规律清零,这样零点个数是 O(log3n) 的,且两个零点之间的序列成波动状,这个求区段就会很简单,改完之后发现这个 ai 其实没什么变化所以你 O(nlog3n) 就能枚举修改下标跑完。然后更牛逼的是两个零点间的这些元素你挑一个原本要减的改一下会发现改完三格之内序列会恢复到一个相同的规律,除非说你改到这一段末尾了。然后求答案的时候你扫一下 ai,ai+3,ai+12 这三个位置特判一下,单个段询问一下 O(log3n),复杂度 O(log32n)

然后就是到 n,n1 的时候恢复不回去了所以你特判一下。

省选模拟7

100+50+0,致敬传奇模拟赛之排行榜分数极差15pts。

A.电车

改一个非质数的话质数那块也是要改的,所以直接考虑质数的互换,两个质数能交换当且仅当在 [1,n] 范围内影响到的数字个数相同,就是 npi=npj,数论分块一下同 [l,r] 内的全部质数可以互换,没了。

B.波长

大便。

f(x,di) 表示把最大子段和砍到 x 要在 [1,i] 操作次数的前缀和,则 i>j,k=jiak(didj1)x,didi1。这个长得特别差分约束,但是比较蛋疼,设 Di=dii=1iai,则原式等价于

DiDi1+ai

DiDj1x

第二个就是可以选择对一段连跳若干个 x,这个段数是 O(n) 的,每次可以贪心取最大子段和然后将区间反转。

进而答案的取值变为若干个一次函数形成一个下凸包 F(x),答案为对于直线 y=Ki=1F(i)=KF(i),考虑维护一个线凸包,一段答案可以用等差数列求出。

综上需要:一个线凸包的维护和一个支持查最大子段和和区间反转的数据结构,考虑用线段树维护区间最大子段和和区间最小子段和答案所在区间下标和反转tag,std给了一种非常简洁的实现。

#include<bits/stdc++.h>//34390
#define int long long
#define MAXN 100005
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define db long double
using namespace std;
int n,a[MAXN];
const int mod=998244353;
int K;
struct Segment_Tree{
	struct data{
		int v,l,r;
		data(){}
		inline data(int _v,int _l,int _r){v=_v,l=_l,r=_r;}
		inline bool operator<(const data &X)const{return v<X.v;}
		inline data operator+(const data &X)const{return data(v+X.v,l,X.r);}
	};
	struct seg{
		data sum,lans,rans,xv;
		seg(){}
		inline seg(data _s,data _l,data _r,data _x){sum=_s,lans=_l,rans=_r,xv=_x;}
		inline seg operator+(const seg &X)const{
			return seg(sum+X.sum,max(lans,sum+X.lans),max(X.rans,rans+X.sum),max({xv,X.xv,rans+X.lans}));
		}
	};
	struct Node{
		Node *ls,*rs;
		bool tag;seg X,N;
		inline void push_up(){assert(ls&&rs);X=ls->X+rs->X,N=ls->N+rs->N;}
		inline void down(){swap(X,N),tag^=1;}
		inline void spread(){if(!tag)return ;ls->down(),rs->down(),tag=0;}
	};
	Node *rt;
	vector<Node>tree;
	inline void build(int l,int r,Node *&p){
		tree.emplace_back();
		p=&tree.back();
		if(l==r){
			p->X.sum=p->X.lans=p->X.rans=p->X.xv=data(a[l],l,l);
			p->N.sum=p->N.lans=p->N.rans=p->N.xv=data(-a[l],l,l);
			return ;
		}
		int mid=l+r>>1;
		build(l,mid,p->ls);
		build(mid+1,r,p->rs);
		p->push_up();
	}
	inline void modify(int l,int r,int ul,int ur,Node *p){
		if(l>=ul&&r<=ur){p->down();return ;}
		p->spread();
		int mid=l+r>>1;
		if(ul<=mid)modify(l,mid,ul,ur,p->ls);
		if(ur>mid)modify(mid+1,r,ul,ur,p->rs);
		p->push_up();
	}
}ST;
vector<pii>sav;
int ans;
inline void insert(int k,int b){//convexhull maintain
	while(sav.size()>1){
		pii A=sav.back(),B=sav[sav.size()-2];
		db V=(db)(A.se-B.se)/(A.fi-B.fi);
		if(V*k-b<=V*A.fi-A.se)sav.pop_back();
		else break;
	}
	sav.emplace_back(mp(k,b));
}
signed main(){
//	freopen("hacho14.in","r",stdin);
	scanf("%lld%lld",&n,&K);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	ST.tree.reserve(2*n+5);
	ST.tree.clear();
	ST.build(1,n,ST.rt);
//	printf("ced\n");
	sav.emplace_back(mp(0,0));
	int w=0;
	for(int i=1;i<=n;i++){
		if(ST.rt->X.xv.v<=0)break;
		w+=ST.rt->X.xv.v;
		insert(i,w);
		ST.modify(1,n,ST.rt->X.xv.l,ST.rt->X.xv.r,ST.rt);
	}
	sort(a,a+1+n);
	reverse(a,a+1+n);
	for(int i=sav.back().fi+1;i<=n;i++)
		if(a[i]<=0){
		w+=a[i],insert(i,w);
	}
//	for(int i=0;i<sav.size();i++)if(sav[i].fi==34390){
//		printf("What the fuck\n");
//		return 0;
//	}
	int L=ceil((db)(sav.back().se-K)/sav.back().fi),R=0;
	bool flag=0;
	while(sav.size()>1){
		pii A=sav.back();
		sav.pop_back();
		pii B=sav.back();
        db V=(db)(A.se-B.se)/(A.fi-B.fi);
        if (V<L){
            L=ceil((db)(B.se-K)/B.fi);
            continue;
        }
        R=ceil(V);
        if(L<R){
            if(!flag)ans=(K+(A.fi*L-A.se))%mod*L%mod,flag=1;
            ans=(ans+(__int128)(L+R+1)*(R-L)/2%mod*A.fi-R+L)%mod;
        }
        L=R;
    }
	printf("%lld\n",(ans%mod+mod)%mod);
	return 0;
}

C.捕获

没改嘻嘻。

省选模拟8

30+20+0,网络流专题说是。

AGC031E

题面是二改的,令人忍俊不禁。

限制和数据范围是难以处理的,所以用网络流解决(?),比如现在考虑限制 Li 不超过 bi 个,可以认为第 bi 个在 Li 右侧,进而就能求出对于一个维度的第 i 个点的坐标下界。

话是这么说的但是网络流没法四个维度一起考虑。考虑枚举选了 K 个点那根据每个点可能的坐标范围就可以对一个坐标求出一个点能否成为从左往右/从上往下第 x 个点。

所以你考虑两个维度进行坐标的选,大概是这么个意思。

wii 的价值,然后跑最大费用最大流,在maxflow=k后可以算答案。

CF1572D

显然匹配点形成二分图,按二进制1个数奇偶性分组之后左部点连一些右部点和汇点然后右部点连一个和超汇有K流量限制的假汇点就可以费用流。

但是边数严重超时,发现一个人匹配后会踢掉 O(n) 条边,但实际上分掉的是两个人,所以匹配 k 次就会卡掉 k(2n1) 种匹配,反过来看如果有 2nk 种匹配那应该就一定存在一组两两匹配了,所以只保留最大的 2nk 条边即可。另外可以点优化,连边的时候没这个点再开点,就会很快,排序用快排,写丑的桶排非常慢。

CF708D

一直以为给的图是一个最大流图哈哈。现在看是简单题不知道为啥考场上没一点想法。

考虑如何将原图修改成可行的。对于 fc 显然没有别的边的需求是不必改的,要改就是要么花 1 的费用增加或减少的流量 cf,f 的,再就是用 2 的费用无限制扩大 f,c。对于 f>c 显然要强制扣掉 fc 先变成合法的,然后可以给变成一个 [c,f] 建德满流(这个就不用花费了) 或者砍成 f<c 的不满流,这个要费用,也可以花2无线扩大。

注意原图中的流量是必须保证的,建完图之后跑最小费用可行流。

省选模拟9

喜欢出原题。

100+70+100

A.染色

维护黑点到根的路径,节点答案可以用换根求出,换根式子内的系数可以用树剖维护,比原题简单。

B.寝室管理

对于树的情况:点分治后树状数组即可,也可以用多项式卷一下,没必要。

对于基环树的情况,拆一条环边然后维护每个树的答案,每个树之间按距离乘一下就完事了。

C.基因合成

原题。[CERC2014]Virus synthesis。

省选模拟10

40+100+20

A.矩形

你先敲个50pts,把维护子段和的线段树改成可持久化树,一行一个树然后set维护行最大连续区间*最大高度。挂分。

B.往事

喜欢出原题,记一下。

求Trie树上lcs+lca深度(lcp)和的最大值。给Trie建一个广义SAM,根据性质parent树上两点的lcs就是他们的lca长度,建完跑lca然后存一下可能作为答案的点跑set启发式合并,set按dfn排完序可以直接找前后继求答案。

C.时空穿梭

第一道场上出正解的数论,但是让卡常了,而且正解没时间打嘻嘻。

枚举左下右上端点 (i,j),(x,y),两点之间的整点为 (gcd(xi,yj)1),贡献就是 (gcd(xi,yj)1C2)

考虑二维的情况,答案应为

d=1n(d1C2)i=1nd1=1nij=1md2=1mj[gcd(d1,d2)=d]

=d=1n(d1C2)x=1nμ(x)i=1nd1=1nid[x|d1]j=1md2=1mjd[x|d2]

带入然后指标换一下

=d=1n(d1C2)x=1nμ(x)i=1nidxj=1mjdx

T=dx,考虑 f(n,T)=i=1niT 怎么算。

发现这个东西分为 [1,T1],[T,2T1]...[xt,nTT1] 的整块,这一部分每段长为 T 而且贡献依次为 0,1,2..nT1,剩下不足成段的拿分块思路求即可。

f(n,T)=TnT(nT1)2+nT(nnTT+1)

Ans=d=1n(d1C2)d|Tμ(Td)f(n1,T)f(m1,T)

=T=1nf(n1,T)f(m1,T)d|T(d1C2)μ(Td)

后面一部分可以杨辉三角后调和级数预处理,答案扩展到 n 维就是

T=1mmaxF(T)i=1nf(mi1,T),F(T)=d|T(d1C2)μ(Td)

这样就可以 O(Tnm) 求解了,再优化是我懒得看的,然后直接卡卡常就过了。

省选模拟11

0(100)+5+5

A.划分

卧槽比赛好难没分卧槽我他妈跟t1爆了卧槽我t1切了卧槽出分了卧槽ub是什么歌??卧槽rk1->rk倒1不嘻嘻。

这个看着就特别不能dp,还是二进制最优化题那就考虑贪心,经过构造发现,假设 n>m 可以前面全给b后面全给a,然后调整a,因为a终究更长所以收益总的来说大所以1就给a了0的话就拿掉b里先前最后的1这样发现给a移位之后每个1最后表示出的数是一样的所以一波操作的收益其实是+a加的那个1位-b扣的那个1位,显然前者递减后者递增发现这样调整不优之后就退出。

B.树

不会。

C.划分树

省选模拟12

0+100+15

A.逆序对

白送56但是来不及写了嘻嘻。

就是你先写一个暴力状压,然后时空爆炸,然后考虑优化状态数,可以通过诡异的数学证明把状态数干成 3n/3,写个hash表存状态dp,或者写一个长度进制数存状态,跑的比较快。

B.网格图

这不是我们燕鸥吗怎么跑到这了。

n=3之前写了,n=2的话一样考虑分治,然后上面的较优当且仅当满足一个偏序上树状数组,n=2本来跑的就快拿最短路写得了就别分讨大dp了。

C.种苹果

诡异题目,这个叫做大容量小值域背包,因为物品大小只有12345然后直接对余数分类来dp,这个东西还满足决策单调性你写个单队就行,也可以整体二分,后者比较短。

#include<bits/stdc++.h>
#define MAXN 300005
#define int long long
using namespace std;
const int mod=998244353,bas=20201205;
int n,b[MAXN],V,ans;
int dp[6][5*MAXN];
vector<int>W[6],sum[6];
inline void dfs(int now,int lst,int l,int r,int L,int R){
    if(L>R)return;
    int mid=L+R>>1,k=-1;
    for(int i=max(l,mid-(int)W[now].size()),xr=min(mid,r);i<=xr;i++)
        if(k==-1||dp[now-1][i*now+lst]+sum[now][mid-i]>dp[now-1][k*now+lst]+sum[now][mid-k])k=i;
    dp[now][mid*now+lst]=dp[now-1][k*now+lst]+sum[now][mid-k];
    dfs(now,lst,l,k,L,mid-1);
    dfs(now,lst,k,r,mid+1,R);
}
signed main(){
    freopen("apple.in","r",stdin);
    freopen("apple.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&b[i]),V+=b[i];
    for(int i=1,c;i<=n;i++){
        scanf("%lld",&c);
        W[b[i]].emplace_back(c);
    }
    for(int i=1;i<=5;i++){
        sort(W[i].begin(),W[i].end(),greater<>());
        int s=0;
        sum[i].emplace_back(0);
        for(int x:W[i])s+=x,sum[i].emplace_back(s);
    }
    for(int i=1;i<=5;i++){
        for (int r=0;r<i;r++)dfs(i,r,0,(V-r)/i,0,(V-r)/i);
    }
    int hp=1;
    for (int i=0;i<=V;i++){
        hp=hp*bas%mod;
        ans=(ans+hp*(dp[5][i]%mod)%mod%mod+mod)%mod;
    }
    printf("%lld",ans%mod);
    return 0;
}

省选模拟13

65+24+40

A.区间

有一个事情就是记区间最大值为 V,长度为 L 则这段区间最后等于的 2x 满足 VxV+logL,这个是好证的。

树上全点对路径问题考虑点分治怎么数组上子序列问题就忘了分治了,我是傻逼吗。对于很大的值的取等可以用hash处理,光速幂预处理幂次。然后就是在序列上分治,跑出来一个最大值然后考虑短的一侧对长侧的贡献。按上面的结论直接枚举可能取等的和然后hash判贡献。

B.圣诞树

考场上一秒想到2sat,然后我一想感觉方案是难以得到的然后弃了。哈哈。

考虑这样的建模:设 vdci,u,1/0 表示第 i 个礼物是否在节点 u 子树中。在一节点则一定在节点父亲,在节点一儿子则一定不在节点另外儿子,这个是基本的树应该保证的。对于询问发现位置可重,看似难以处理实则只需不刻意限制,考虑这样:a 不在 c 子树则 b 一定要在 c 子树,ac 的儿子的子树则 b 一定不在那个子树,前者就可以有效规避"a在b不在"状连边导致的无法处理两者同在 c 或者一者在 c 另一者在子树的问题。

连边和逆反命题,对每个点从根往下一直找合法的儿子直到不能跳为止。

C.神奇国度

没看懂。

省选模拟14

70+10(60)+0

打的啥。

A.数

坠机题,开考之后急的要死两个小时没分,糊了个70走人了,不知道在急什么。

列个不等式然后两边式子一提,贡献可以枚举 xy,x+y 单独计算,没了。

B.树

一眼轮状病毒+polya。答案就是

Ans=1nd|nϕ(nd)F(d)

F(d) 就是不考虑旋转同构时的答案,就是轮状病毒那题。考场上一直算着 F(3)=18 然后就以为不是polya,唐唐的,剩二十分钟顿悟了没时间调。

需要一些手法,比如那个 1n 因为模数任意就会特别蛋疼,然后就考虑一种手法就是把 modmodn,最后给答案就能正常除了,又是从未出现的经典技巧呢。然后 Fi=3Fi1Fi2+2 这样矩快是 27 常数的,然后难发现 Fi=f2i+f2i22,其中 fi 就是斐波那契数列,这样矩快就会比较极速,还可以预处理 2i 阶系数矩阵用二进制拆解算答案,会快一些。

不难,但是没改。

省选模拟15

60+0(10)+0(20)

喜欢打完暴力不拍。

A.单峰序列

60pts可以贪心,考场上一直在写假,但是最后还是真了要不然保龄了。

从小到大填每次考虑向左向右较小的那个放。考虑动态加入的过程对左侧是没有影响的,右侧则递增,对于新加入的这个值影响的也是大于它的那一部分,状态的影响其实可以用一个线段树均摊实现。

B.划分线段

这个是树,但是合并就比较恶心了,因为贡献的合并或者最优化没法直接在当前节点处理,然后tj给出了一个比较牛逼的操作就是设 dpu,i,x,y 表示的是节点 u 要确认 i 个段,当前 u 左右侧是否有未补全的段,这个 i 是总不小于 sizu 的,意思就是在确立 u 子树的这些段的基础上我又提前预留了 isizu 个父亲节点的位置。x,y 的有无就是说要不要有这个节点的一段延伸出去和其他一个节点拼起来。

对于叶子节点的初始化有

		dp[u][0][0][0]=sav[u].se-sav[u].fi;
		dp[u][0][0][1]=-sav[u].fi;
		dp[u][0][1][0]=sav[u].se;
		dp[u][0][1][1]=0;

第二维为了省空间自减了一个 sizu,如果不预留其他的段的话贡献可以直接在当前节点处理成 rl,否则有残留一段的情况下就只保留一部分前缀和的贡献,如果两边都有的话就现不在这块处理贡献了,因为它这一段以后会和左右侧分别合并。

对于合并两个子树 u,v 的过程,本质上是在做 dpu,s1+s2+y,x,zdpu,s1,x,y+dpv,s2,y,zy 是要求同步的因为定义,而且如果都有预留段的话就在这个时候合并即段数+1。跑完输出就行了。

另外转移到最后一个子树时要进行消预留段的操作就是

				if(x)dp[u][i][0][y]=max(dp[u][i][0][y],g[typ][i][x][y]-sav[u].fi);
				if(y)dp[u][i][x][0]=max(dp[u][i][x][0],g[typ][i][x][y]+sav[u].se);

还有一个比较手法的补足操作

if(x||y)dp[u][i][x][y]=max(dp[u][i][x][y],g[typ][i][x][y]);

这个的意思是,如果我的两侧有至少一个待补足的段,我可以让它们在我中间就被补足,从而留下来至少一个空缺的段,此时有 dpu,i,x,ydpu,i+1,x,y 因为 sizu=sizu+1 所以在移位后直接就是i->i但是如果没有待补足的段我肯定不能凭空预留出来一个,所以有一个 x||y 的前置条件。

C.红蓝树

想这样一个事情:每个右括号的颜色都会影响到它右侧的第一个左括号的颜色,显然这是一个多对一的关系,进而这样的影响关系当形成一棵树,如果 r=n 的话就是整棵树的一部分的某个蓝点会让它的整个根链变蓝。

先预处理出这个树,并每次遇到一个新的左括号就新开一个节点同时也是这个节点的 dfn,这样就实现了如果 dfnx<dfny 则两点在原序列的位置也一定满足 x<y,这个很重要,设想一下整棵树的dfn是倒过来的。

现在不考虑反转颜色的操作,则栈信息可以用线段树来合并,具体地对于一个dfn 小的左子树和大的右子树(认为子树已经合并完成),考虑对每个节点维护区间内最小和最大的蓝色点dfn,即答案,则合并时相当于是额外加了左子树最大->右子树最小这一段的dfn的蓝色点贡献,这一部分可以直接倍增求出。

inline int getval(int l,int r){
	int res=1;
	if(!l)return 0;
	for(int i=MAXK-1;i>=0;i--)if(father[i][l]<r)res+=(1<<i),l=father[i][l];
	return res;
}

然后就可以合并了,查询的时候查一下两端最近的有效点的答案,最后把剩下来的一段 [ans.xdfn,r+1) 单独算个贡献即可。

对于有修改的情况,你会发现这个线段树的维护性能有点过于牛逼了,直接同时维护红点和蓝点的信息,反转的时候打个rev标记就行,然后就做出来了。

关于一个corner case:当且仅当两节点都有蓝点的时候才进行合并贡献,否则只进行最大最小值的维护和答案简单相加,知道某一次出现两侧都有蓝点时才进行合并,如果自始至终只有左侧某一个蓝点则会在最后对 [ans.xdfn,r+1) 这一段处理时加上。

posted @   Cl41Mi5deeD  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示