nfls

我决定每天都写随笔。

大抵是觉得自己太菜了吧。

9.11

\(10611\)

A.

为什么赛时不会呢?

每条管道没有流量或者有单向的流量,每个点处流入的流量之和等于流出的流量之和。

这句话的意思是说对于一个点,至多有一条与之连接的边可以不查询。

所以想到对于那些不查询的边,构成了一片森林。

为使得总代价尽可能的小,我们可以让不查询的森林的全职总和尽可能的大,于是变成了一道最大生成森林。

负权边特殊处理一下即可。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define ll long long
int n,m,tot;
struct edge{
	int x,y,w;
}e[N];
int fa[N];
int find(int x){
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
bool cmp(edge a,edge b){ return a.w>b.w; }
ll ans;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		if(w<0) ans+=w;
		else e[++tot]={x,y,w};
	}
	sort(e+1,e+tot+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=tot;i++){
		int x=find(e[i].x),y=find(e[i].y);
		if(x==y){
			ans+=e[i].w;
			continue;
		}
		fa[x]=y;
	}
	printf("%lld",ans);
	return 0;
}

B.

有傻逼赛时写了个双 \(\log\) 巨大麻烦的做法因为数组开小狂砍 \(40 pts\) ,是谁呢。

先说一下我的思路。

一开始的时候我拓扑排序,直接放,对拍发现被 hack 了。

6
2 5 4 2 4 2 
2 1
3 2
4 2
5 1
6 2

这样会输出 \(10\),但实际上答案是 \(9\)

考虑按照 \(a_i+dep_i\) 从大到小排序,最大的尽可能的先放,只可能在 \([0,n-1]\) 的时刻出发。

对于一个点,它要在它子树内所有节点放完之后才能放,所以我们在这个节点上挂一段区间 \([l,r]\) 表示这个节点的子树应该在 \([l,r]\) 的顺序里出发。

然后线段树求一下距离最近的被标记过的祖先节点,这就是 这道题

先放一下 \(O(n^2)\) 的代码。

#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,ans;
int a[N],t[N];
int dep[N],dfn[N],siz[N],f[N],tot;
vector<int> g[N];
int l[N],r[N],vis[N];
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	dfn[x]=++tot;
	siz[x]=1,f[x]=fa;
	for(auto y:g[x]) if(y!=fa) dfs(y,x),siz[x]+=siz[y];
}
struct node{ int x,id; }b[N];
bool cmp(node x,node y){ return x.x>y.x; }
int query(int x){
	while(!vis[x]) x=f[x];
	return x;
}
void modify(int x){
	int c=siz[x];
	while(!vis[x]) siz[x]-=c,x=f[x];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),t[i]=i;
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dep[0]=-1;
	dfs(1,0);
	for(int i=1;i<=n;i++) b[i]={a[i]+dep[i],i};
	sort(b+1,b+n+1,cmp);
	vis[0]=1;
	for(int i=1;i<=n;i++){
		int id=b[i].id,x=query(id);
		l[id]=l[x],r[id]=l[id]+siz[id]-1;
		ans=max(ans,r[id]+b[i].x);
		l[x]+=siz[id];
		modify(id);
		vis[id]=1;
	}
	printf("%d",ans);
	return 0;
}

优化完之后是 \(O(n \log ^2 n)\) 的。

#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,ans;
int a[N];
int dep[N],siz[N],f[N],mxson[N];
int dfn[N],top[N],pos[N],tot;
vector<int> g[N];
void dfs1(int x,int fa){
	dep[x]=dep[fa]+1;
	siz[x]=1,f[x]=fa;
	for(auto y:g[x]){
		if(y==fa) continue;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[mxson[x]]<siz[y]) mxson[x]=y;
	}
}
void dfs2(int x,int topf){
	dfn[x]=++tot;
	pos[tot]=x;
	top[x]=topf;
	if(!mxson[x]) return;
	dfs2(mxson[x],topf);
	for(auto y:g[x]) if(y!=f[x]&&y!=mxson[x]) dfs2(y,y);
}
struct node{ int x,id; }b[N];
bool cmp(node x,node y){ return x.x>y.x; }
struct tree{
	int l,r,siz,d;
}t[N*4];
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	t[p].siz=t[p].d=0;
	if(l==r){
		t[p].siz=siz[pos[l]];
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}
int query(int p,int x){
	if(t[p].l==t[p].r) return t[p].siz;
	int mid=(t[p].l+t[p].r)>>1;
	int res=t[p].siz;
	if(x<=mid) res+=query(p<<1,x);
	else res+=query(p<<1|1,x);
	return res;
}
void modify(int p,int l,int r,int v){
	if(t[p].l==l&&t[p].r==r){
		t[p].siz+=v;
		return;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) modify(p<<1,l,r,v);
	else if(l>mid) modify(p<<1|1,l,r,v);
	else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
}
void add(int x,int y,int v){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		modify(1,dfn[top[x]],dfn[x],v);
		x=f[top[x]];
	}
	if(dep[x]<dep[y]) swap(x,y);
	modify(1,dfn[y],dfn[x],v);
}
void update(int p,int l,int r,int x){
	if(t[p].l==l&&t[p].r==r){
		if(dep[t[p].d]<dep[x]) t[p].d=x;
		return;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) update(p<<1,l,r,x);
	else if(l>mid) update(p<<1|1,l,r,x);
	else update(p<<1,l,mid,x),update(p<<1|1,mid+1,r,x);
}
int q(int p,int x){
	if(t[p].l==t[p].r) return t[p].d;
	int ans=t[p].d;
	int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid){
		int t=q(p<<1,x);
		if(dep[t]>dep[ans]) ans=t;
	}else{
		int t=q(p<<1|1,x);
		if(dep[t]>dep[ans]) ans=t;
	}
	return ans;
}
int l[N],r[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dep[0]=-1;
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=n;i++) b[i]={a[i]+dep[i],i};
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;i++){
		int id=b[i].id;
		int s=query(1,dfn[id]);
		int x=q(1,dfn[id]);
		l[id]=l[x],r[id]=l[id]+s-1;
		ans=max(ans,r[id]+b[i].x);
		l[x]+=s;
		add(x,id,-s);
		update(1,dfn[id],dfn[id]+siz[id]-1,id);
	}
	printf("%d",ans);
	return 0;
}

很小丑,直接二分可以做到 \(O(n \log n)\)

C.

题解很清楚了。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define pii pair<int,int>
#define ll long long
int n,m;
vector<pii> g[N];
int a[N];
struct tree{
	int l,r;
	ll f,val,tag;
}t[N*4];
void pushup(int p){
	t[p].f=max(t[p<<1].f,t[p<<1|1].f);
	t[p].val=max(t[p<<1].val,t[p<<1|1].val);
}
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	t[p].f=t[p].val=t[p].tag=0;
	if(l==r){
		t[p].val=t[p].f=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void pushdown(int p){
	if(t[p].tag>t[p<<1].tag){
		t[p<<1].tag=t[p].tag;
		t[p<<1].f=max(t[p<<1].f,t[p<<1].val+t[p].tag);
	}
	if(t[p].tag>t[p<<1|1].tag){
		t[p<<1|1].tag=t[p].tag;
		t[p<<1|1].f=max(t[p<<1|1].f,t[p<<1|1].val+t[p].tag);
	}
}
void modify(int p,int l,int r,int v){
	if(t[p].l==l&&t[p].r==r){
		if(v>t[p].tag){
			t[p].tag=v;
			t[p].f=max(t[p].f,t[p].val+v);
		}
		return;
	}
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) modify(p<<1,l,r,v);
	else if(l>mid) modify(p<<1|1,l,r,v);
	else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
	pushup(p);
}
int stk[N],tp;
ll ans[N]; 
ll query(int p,int l,int r){
	if(t[p].l==l&&t[p].r==r) return t[p].f;
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) return query(p<<1,l,r);
	else if(l>mid) return query(p<<1|1,l,r);
	else return max(query(p<<1,l,mid),query(p<<1|1,mid+1,r));
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		g[l].push_back({r,i});
	}
	build(1,1,n);
	for(int i=n;i>=1;i--){
		for(int j=tp;j>=1;j--){
			if(j!=tp&&a[stk[j+1]]>=a[i]||2*stk[j]-i>n) break;
			modify(1,2*stk[j]-i,n,a[i]+a[stk[j]]);
		}
		while(tp&&a[i]>=a[stk[tp]]) tp--;
		stk[++tp]=i;
		for(auto y:g[i]) ans[y.second]=query(1,i,y.first);
	}
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

D.

留坑,但大抵不会再补了。


\(20035\)

A.

简单题,怎么做都行,并查集查一下 \(sum_{find(1)}\) 什么时候合法即可。

我又小丑了,这个题完全不用二分,正着做就行。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
int n,w;
struct node{ int x,y,w; }e[N];
int l,r;
int fa[N],a[N];
ll sum[N];
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y) return;
	fa[x]=y;
	sum[y]+=sum[x];
}
bool check(int x){
	for(int i=1;i<=n;i++) fa[i]=i,sum[i]=a[i];
	for(int i=1;i<n;i++)
		if(e[i].w<=x) merge(e[i].x,e[i].y);
	return sum[find(1)]>=w;
}
int main(){
	scanf("%d%d",&n,&w);
	for(int i=2;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
		r=max(r,e[i].w);
	}
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)) r=mid-1;
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}

B.

先按画框大小从大到小排序,画框肯定选择前面的连续个。

再按照美观度给画从大到小排序,当美观度相同时,必然选大小最大的,把小的留给后面的不是很大的画框。

能选就选,不能选就往后看,贪心即可,简单题。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
struct node{ int s,v; }a[N]; 
int c[N];
bool cmp1(node a,node b){
	if(a.v==b.v) return a.s>b.s;
	return a.v>b.v;
}
bool cmp2(int x,int y){ return x>y; }
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].s,&a[i].v);
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<=m;i++) scanf("%d",&c[i]);
	sort(c+1,c+m+1,cmp2);
	int tp=1,ans=0;
	for(int i=1;i<=n;i++) if(tp<=m&&a[i].s<=c[tp]) ans++,tp++;
	printf("%d",ans);
	return 0;
}

C.

一道很好的题。

考虑序列上怎么做,设 \(dp_i\) 表示到第 \(i\) 个位置时的分段数方案。

转移,对于每一个 \(gcd(j...i) > 1\)\(j\) ,都有 \(dp_i+=dp_{j-1}\)

所以设 \(s_i\) 为前 \(i\) 项 dp 值的前缀和,那么本质就变成了找到最后一个 \(gcd(j...i) = 1\) 的位置 \(j\)\(dp_i=s_{i-1}-s_{j-1}\)

找的过程可以用二分和 st 表解决。

但这个题是环,断环为链,从 \(1\) 断开,枚举结尾几个元素和 \(1\) 号元素并在一起。

\(1\) 号元素并起来的时候大小改变,就重新做一次 dp ,容易发现只有至多 \(\log\) 次改变,所以总复杂度 \(O(n \log^2 n)\)

细节是当每次做 dp 的时候都有可能多算了一种方案(仅在 \(gcd(1...n)>1\) 时有效) ,所以符合条件时应减掉这种情况。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
const int N=1e5+5;
int n,gc;
int a[N];
ll ans,dp[N],sum[N];
int g[N][25];
int check(int l,int r){
	int k=(int)log2(r-l+1);
	return __gcd(g[l][k],g[r-(1<<k)+1][k]);
}
void solve(int n){
	for(int i=1;i<=n;i++) g[i][0]=a[i];
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++) g[i][j]=__gcd(g[i][j-1],g[i+(1<<(j-1))][j-1]);
	dp[0]=sum[0]=1;
	for(int i=1;i<=n;i++){
		int l=1,r=i-1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(mid,i)==1) l=mid+1;
			else r=mid-1;
		}
		if(!r) dp[i]=sum[i-1];
		else dp[i]=(sum[i-1]-sum[r-1]+mod)%mod;
		sum[i]=(sum[i-1]+dp[i])%mod;
	}
}
int main(){
	scanf("%d",&n);
	int f=0;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),f|=(a[i]==1),gc=__gcd(gc,a[i]);
	if(f){
		printf("0");
		return 0;
	}else if(n==1){
		printf("1");
		return 0;
	}
	solve(n);
	ans=dp[n];
	for(int i=n;i>=2;i--){
		int lst=a[1];
		a[1]=__gcd(a[1],a[i]);
		if(a[1]==1) break;
		if(a[1]!=lst) solve(i-1);
		ans=(ans+dp[i-1])%mod;
		if(gc>1) ans=(ans-1+mod)%mod;
	}
	printf("%lld",ans);
	return 0;
}

D.

一道很好的题,树哈希。

考虑两棵子树,当它们按照顺序的每个节点的出度均相同,即它们 \(dfs\) 序上的哈希值一样时,相同。

所以当成字符串哈希就行了。

注意到是距离大于 \(k\) 的子树需要删掉,这体现在合并 \(dfs\) 序的时候不合并这一段即可。

另外值得一提的是,如果直接二分,需要寻找 \(k\) 级祖先,这样做是双 \(\log\) 的。

但如果我们换一种方式,直接倍增,检查是否合法,如果合法就直接把祖先更新成这次的祖先,这样做复杂度就降至 \(O(n \log n)\)

#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N=1e5+5;
int n;
int pos[N],l[N],r[N],tot;
int f[N][25];
vector<int> g[N],v[N];
ull bas[N],h[N];
void dfs(int x){
	l[x]=++tot;
	pos[tot]=x;
	for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];
	h[tot]=h[tot-1]*N+g[x].size();
	for(auto y:g[x]) dfs(y);
	r[x]=tot;
}
void calc(ull &x,int l,int r){
	if(l>r) return;
	x=x*bas[r-l+1]+h[r]-h[l-1]*bas[r-l+1];
}
unordered_set<ull> s;
int a[N],b[N];
bool check(int x){
	s.clear();
	for(int i=1;i<=n;i++) v[i].clear();
	for(int i=1;i<=n;i++){
		b[pos[i]]=f[a[pos[i]]][x];
		if(b[pos[i]]) v[b[pos[i]]].push_back(pos[i]);
	}
	for(int i=1;i<=n;i++){
		if(v[i].empty()) continue;
		int lst=l[i];
		ull res=0;
		for(auto y:v[i]){
			calc(res,lst,l[y]-1);
			lst=r[y]+1;
		}
		calc(res,lst,r[i]);
		if(s.find(res)!=s.end()) return 1;
		s.insert(res);
	}
	return 0;
}
int ans;
int main(){
	scanf("%d",&n);
	bas[0]=h[0]=1;
	for(int i=1;i<=n;i++){
		bas[i]=bas[i-1]*N;
		a[i]=i;
		int c;
		scanf("%d",&c);
		while(c--){
			int x;
			scanf("%d",&x);
			g[i].push_back(x);
			f[x][0]=i;
		}
	}
	dfs(1);
	for(int i=20;i>=0;i--){
		if(check(i)){
			ans|=(1<<i);
			for(int j=1;j<=n;j++) a[j]=b[j];
		}
	}
	printf("%d",ans);
	return 0;
}

9.12

\(10611\)

A.

考虑距离不超过 \(2\) 怎么解决,设 \(tag_{x,0}\) 表示这个点自己的异或值,\(tag_{x,1}\) 表示该节点子树中与之距离为 \(1\) 的点的异或和,\(tag_{x,2}\) 表示该节点子树中与之距离为 \(2\) 的点的异或和。

对于每一个点 \(x\),答案为 \(tag_{fa,1} \oplus tag_{fa,0} \oplus tag_{gfa,0} \oplus tag_{x,1} \oplus tag_{x,2}\)

对于修改操作,一个点的更改只会影响到 \(tag_{x,0}\)\(tag_{fa,1}\)\(tag_{gfa,2}\),分别修改即可。

upd:果然挂了,有两个问题,第一个是 res*i*i 可能会爆 LL,另一个问题是如果这个点是根节点,那么它自己就不会被算到,\(100 -> 10\)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
const int mod=1e9+7;
int n,q;
vector<int> g[N];
int f[N];
ll tag[N][3];
int a[N];
void dfs(int x,int fa){
	f[x]=fa;
	tag[x][0]=a[x];
	if(f[x]) tag[f[x]][1]^=a[x];
	if(f[f[x]]) tag[f[f[x]]][2]^=a[x];
	for(auto y:g[x]) if(y!=fa) dfs(y,x);
}
ll ans;
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs(1,0);
	for(int i=1;i<=q;i++){
		int x;
		ll v;
		scanf("%d%lld",&x,&v);
		if(f[x]) tag[f[x]][1]^=(tag[x][0]^v);
		if(f[f[x]]) tag[f[f[x]]][2]^=(tag[x][0]^v);
		tag[x][0]=v;
		ll res=tag[x][1]^tag[x][2];
		res^=tag[f[x]][1]^tag[f[x]][0]^tag[f[f[x]]][0];
		if(x==1) res^=tag[x][0];
		ans=(ans+res*i%mod*i%mod)%mod;
	}
	printf("%lld",ans);
	return 0;
}

B.

呃呃赛时又不会。

留坑待补。

C.

做差分得到一些位置上是 \(1\)\(1\) 的个数不会超过 \(2\cdot k\) 。也就是说每次选两个距离为奇质数的点可以消掉两个 \(1\)

由此可见,当两个点相差为奇质数,只需要操作一次;当它们相差为偶数,只需要操作两次;其余情况需要三次。

所以我们贪心地想让奇质数匹配尽可能地多。还有一个性质是,两个点的距离为奇质数时,它们的奇偶性一定不同,所以根据奇偶性建二分图,跑匈牙利或网络流都可以通过。

筛法从 \(2\) 开始捏。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5,M=1e7+5;
int k;
int a[M],p[M];
void pre(){
	for(int i=2;i<M;i++)
		if(!p[i])
			for(int j=2;i*j<M;j++) p[i*j]=1;
	p[1]=p[2]=1;
}
int mch[N],used[N];
vector<int> s[2],g[N];
int dfs(int x){
	for(auto y:g[x]){
		if(used[y]) continue;
		used[y]=1;
		if(!mch[y]||dfs(mch[y])){
			mch[y]=x;
			return 1;
		}
	}
	return 0;
}
int ans;
int main(){
	pre();
	scanf("%d",&k);
	while(k--){
		int x;
		scanf("%d",&x);
		a[x]=1;
	}
	for(int i=1;i<M;i++)
		if(a[i]^a[i-1]) s[i&1].push_back(i);
	for(int i=0;i<s[0].size();i++){
		int x=s[0][i];
		for(int j=0;j<s[1].size();j++){
			int y=s[1][j];
			if(!p[abs(x-y)]) g[i+1].push_back(j+1);
		}
	}
	for(int i=0;i<s[0].size();i++){
		memset(used,0,sizeof used);
		ans+=dfs(i+1);
	}
	int a=s[0].size()-ans,b=s[1].size()-ans;
	ans+=a+b;
	a=a&1,b=b&1;
	if(a&b) ans++;
	else if(a) ans++;
	else if(b) ans+=2;
	printf("%d",ans);
	return 0;
}

贪心和数据结构专题

A.

线段树上二分,如果有左右端点限制会很麻烦,所以我只会整个序列上的线段树二分。

首先对于第一个问题,本质上是求序列中第一个大于等于 \(y\) 的位置 \(pos\),然后 \([pos,x]\) 区间赋值为 \(y\)

对于第二个问题,有一个左端点的限制不好做,那我们可以将 \(y\) 加上 \(\sum_{i=1}^{x-1} a_i\) ,这样就等价于前面的东西都可以选一遍,那么只要统计答案的时候减去前面的 \(x-1\) 即可。

但我不是很理解为什么时间复杂度是 \(O(n \log y \log n)\) 的,引用一下 kls 的题解:

每个人进商店买东西构成最多 \(\log y\) 个连续段。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5;
int n,m;
int a[N];
struct tree{
	int l,r;
	ll mn,tag,sum;
}t[N*4];
void pushup(int p){
	t[p].mn=min(t[p<<1].mn,t[p<<1|1].mn);
	t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
}
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	t[p].mn=t[p].sum=t[p].tag=0;
	if(l==r){
		t[p].mn=t[p].sum=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void pushdown(int p){
	if(t[p].tag){
		t[p<<1].mn=t[p].tag,t[p<<1|1].mn=t[p].tag;
		t[p<<1].sum=t[p].tag*(t[p<<1].r-t[p<<1].l+1);
		t[p<<1|1].sum=t[p].tag*(t[p<<1|1].r-t[p<<1|1].l+1);
		t[p<<1].tag=t[p<<1|1].tag=t[p].tag;
		t[p].tag=0;
	}
}
int q(int p,int v){
	if(t[p].l==t[p].r){
		if(t[p].mn<v) return t[p].l;
		return t[p].l+1;
	}
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(t[p<<1].mn<v) return q(p<<1,v);
	else return q(p<<1|1,v);
}
ll qsum(int p,int r){
	if(t[p].r==r) return t[p].sum;
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) return qsum(p<<1,r);
	else return t[p<<1].sum+qsum(p<<1|1,r);
}
int query(int p,ll &v){
	if(v<t[p].mn) return 0;
	if(t[p].l==t[p].r){
		v-=t[p].sum;
		return 1;
	}
	pushdown(p);
	int ans=0;
	if(t[p<<1].sum<=v) v-=t[p<<1].sum,ans+=t[p<<1].r-t[p<<1].l+1;
	else ans+=query(p<<1,v);
	ans+=query(p<<1|1,v);
	return ans;
}
void modify(int p,int l,int r,ll v){
	if(t[p].l==l&&t[p].r==r){
		t[p].tag=t[p].mn=v;
		t[p].sum=v*(r-l+1);
		return;
	}
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) modify(p<<1,l,r,v);
	else if(l>mid) modify(p<<1|1,l,r,v);
	else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
	pushup(p);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build(1,1,n);
	while(m--){
		int op,x;
		ll y;
		scanf("%d%d%lld",&op,&x,&y);
		if(op==1){
			int p=q(1,y);
			if(p<=x) modify(1,p,x,y);
		}else{
			if(x!=1) y+=qsum(1,x-1);
			printf("%d\n",query(1,y)-x+1);
		}
	}
	return 0;
}

一些杂题

G.

好久之前的题了,复习一下网络流。

考虑建图跑费用流,费用流是因为这个题有得分这一权值限制。

源点连角色,流量为 \(1\),权值为 \(c\),角色连接区间,流量为 \(1\),权值为 \(0\),因为是区间,所以不能直接连边,考虑线段树优化建图。

线段树上父亲连孩子,这里很需要注意流量到底是什么,显然我们对于一个区间,可以流过长度大小的流量,权值为 \(0\),叶子结点连接汇点,流量为 \(1\),权值为 \(0\)

至此建图结束,跑最大费用最大流即可,注意费用流板子里的 \(vis\) 数组有什么用:

  1. spfa 时不重复加入队列。

  2. 最短路建出来的生成图不一定是拓扑图,有可能会死循环,在 dfs 时判掉,防止死循环。

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
#define inf 0x3f3f3f3f
int n;
int s,t;
struct edge{
	int to,nxt,w,c;
}e[N*40];
int h[N],now[N],tot=1;
void add(int x,int y,int w,int c){
	e[++tot]={y,h[x],w,c};
	h[x]=tot;
	e[++tot]={x,h[y],0,-c};
	h[y]=tot;
}
int cnt,id[N];
void build(int p,int l,int r){
	id[p]=++cnt;
	if(l==r){
		add(id[p],n+1,1,0);
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	add(id[p],id[p<<1],mid-l+1,0),add(id[p],id[p<<1|1],r-mid,0); //this!!!
}
void modify(int p,int l,int r,int ql,int qr,int x){
	if(l==ql&&r==qr){
		add(x,id[p],1,0);
		return;
	}
	int mid=(l+r)>>1;
	if(qr<=mid) modify(p<<1,l,mid,ql,qr,x);
	else if(ql>mid) modify(p<<1|1,mid+1,r,ql,qr,x);
	else modify(p<<1,l,mid,ql,mid,x),modify(p<<1|1,mid+1,r,mid+1,qr,x);
}
int dis[N];
int ans,vis[N];
bool spfa(){
	for(int i=0;i<=cnt;i++) now[i]=h[i],dis[i]=inf,vis[i]=0;
	queue<int> q;
	q.push(s);
	dis[s]=0;
	int f=0;
	while(!q.empty()){
		int x=q.front();
		if(x==t) f=1;
		q.pop();
		vis[x]=0;
		for(int i=h[x];i;i=e[i].nxt){
			int y=e[i].to,c=e[i].c;
			if(e[i].w>0&&dis[y]>dis[x]+c){
				dis[y]=dis[x]+c;
				if(!vis[y]){
					q.push(y);
					vis[y]=1;
				}
			}
		}
	}
	return f;
}
int dfs(int x,int sum){
	if(x==t) return sum;
	int flow=0;
	vis[x]=1;
	for(int &i=now[x];i;i=e[i].nxt){
		int y=e[i].to,c=e[i].c;
		if(e[i].w>0&&!vis[y]&&dis[y]==dis[x]+c){
			int tmp=dfs(y,min(sum,e[i].w));
			if(!tmp) dis[y]=inf;
			e[i].w-=tmp,e[i^1].w+=tmp;
			sum-=tmp,flow+=tmp;
			ans+=c*tmp;
			if(!sum) break;
		}
	}
	vis[x]=0;
	return flow;
}
void dinic(){ while(spfa()) dfs(s,inf); }
int x[N],y[N],mx;
inline int read(){
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		x[i]=read(),y[i]=read();
		int c=read();
		add(0,i,1,-c);
		y[i]--;
		mx=max(mx,y[i]);
	}
	cnt=n+1;
	build(1,1,mx);
	for(int i=1;i<=n;i++) modify(1,1,mx,x[i],y[i],i);
	s=0,t=n+1;
	dinic();
	printf("%d",-ans);
	return 0;
}

G.

一道线性基和图论综合的题目。

这篇题解写的很清楚了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pil pair<int,ll>
const int N=5e4+5;
int n,m;
vector<pil> g[N];
int vis[N];
ll a[65],dis[N];
void insert(ll x){
	for(int i=60;i>=0;i--){
		if((x>>i)&1){
			if(!a[i]){
				a[i]=x;
				return;
			}
			x^=a[i];
		}
	}
}
void dfs(int x,ll res){
	vis[x]=1;
	dis[x]=res;
	for(auto t:g[x]){
		int y=t.first;
		ll w=t.second;
		if(!vis[y]) dfs(y,res^w);
		else insert(dis[x]^dis[y]^w);
	}
}
ll query(ll x){
	for(int i=60;i>=0;i--) x=max(x,x^a[i]);
	return x;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		ll w;
		scanf("%d%d%lld",&x,&y,&w);
		g[x].push_back({y,w});
		g[y].push_back({x,w});
	}
	dfs(1,0);
	printf("%lld",query(dis[n]));
	return 0;
}

L

几道 dp,毁了我的省队梦。

所以来做网络流了哈哈哈哈!

首先有一个错误的做法,把人和车连边跑最小费用最大流,这样就有一个问题,一个工人只能修一个车(或者一个工人同时修多辆车,这两个问题无法同时解决),所以我们考虑把一个人拆成 \(n\) 个人。

考虑对于一个工人,假设他能修 \(k\) 辆车,这 \(k\) 辆车的修复时间为 \(a_1,a_2,...,a_k\) ,那么第一个人需要等待 \(a_1\) 的时间,第二个人需要等待 \(a_1+a_2\) 的时间,以此类推,第 \(k\) 个人会等待 \(\sum_{i=1}^{k} a_i\) 的时间。

所以总共等待了 \(\sum_{i=1}^{k} a_i \times (k-i+1)\) 的时间。

所以反过来考虑,一个人能为整个等待过程贡献多少的时间呢,显然是 \(a_i \times (k-i+1)\),但这个 \(k\) 是不确定的,所以我们再次转换思路,可以倒着看,一个人作为倒数第 \(i\) 个被修会为等待过程贡献多少时间呢,显然是 \(a_i \times i\),所以以这个作为费用连边跑最小费用最大流即可。

注意一个细节,就是不能每次连到一个分裂的工人的点就顺便连向汇点,因为这样到汇点的边就重复了很多条,导致答案变得很小。

正确处理方法应该是所有中间边连完之后再连后面的边,保证同一个点到汇点的边只有一条。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N=3e3+5,M=5e5+5; 
int n,m,s,t;
int tot=1,h[N],now[N];
struct edge{ int to,nxt,w,c; }e[M];
void add(int x,int y,int w,int c){
	e[++tot]={y,h[x],w,c};
	h[x]=tot;
	e[++tot]={x,h[y],0,-c};
	h[y]=tot;
}
int dis[N],vis[N];
bool spfa(){
	for(int i=s;i<=t;i++) now[i]=h[i],dis[i]=inf,vis[i]=0;
	queue<int> q;
	dis[s]=0;
	q.push(s);
	int f=0;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		if(x==t) f=1;
		for(int i=h[x];i;i=e[i].nxt){
			int y=e[i].to,c=e[i].c;
			if(e[i].w>0&&dis[y]>dis[x]+c){
				dis[y]=dis[x]+c;
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return f;
}
int ans;
int dfs(int x,int sum){
	if(x==t) return sum;
	int flow=0;
	vis[x]=1;
	for(int &i=now[x];i;i=e[i].nxt){
		int y=e[i].to,c=e[i].c;
		if(e[i].w>0&&!vis[y]&&dis[y]==dis[x]+c){
			int tmp=dfs(y,min(sum,e[i].w));
			if(!tmp) dis[y]=inf;
			e[i].w-=tmp,e[i^1].w+=tmp;
			sum-=tmp,flow+=tmp;
			ans+=tmp*c;
			if(!sum) break;
		}
	}
	vis[x]=0;
	return flow;
}
void dinic(){ while(spfa()) dfs(s,inf); }
int main(){
	scanf("%d%d",&m,&n);
	s=0,t=n*(m+1)+1;
	for(int i=1;i<=n;i++){
		add(0,i,1,0);
		for(int j=1;j<=m;j++){
			int x;
			scanf("%d",&x);
			for(int k=1;k<=n;k++) add(i,n*j+k,1,x*k);
		}
	}
	for(int i=1;i<=n*m;i++) add(i+n,t,1,0);
	dinic();
	printf("%.2lf",ans*1./n);
	return 0;
}

I

AC 自动机,好久没写了。

对于 \(S\) 串建立 AC 自动机,那么每次加入一个串 \(T\) ,可以算出这个串对于哪些 \(S\) 有贡献,累加一下即可。

具体来说,建出失配树,每次在 \(Trie\) 树上遍历该次的 \(T\) ,将这个节点在失配树上到根节点的路径都 \(+1\)

但这样就会有一个问题,对于一个 \(T\) ,给每个串贡献了不止一次,所以其实我们要做的等价于将每个节点到根节点这条链合并起来,得到一个点集,将点集中的点 \(+1\)

这个东西其实相当于树链求并,我们把所有点按照 dfs 序排完序后(可以顺便去个重),得到数组 \(a_n\)

对于 \(1\le i \le k\) ,将 \(a_i\) 到根节点的路径 \(+1\)

对于 \(1 \le i < k\) ,将 \(lca(a_i,a_{i+1})\) 到根节点的路径 \(-1\)

路径加,难道还要写树剖?

事实上是不用的,用一个思维转化,单点加,查询子树和即可,这样直接用 dfs 序就可以了。

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int n,q;
int t[N][26],fail[N],tot;
int ed[N];
void insert(char s[],int id){
	int n=strlen(s),pos=0;
	for(int i=0;i<n;i++){
		int x=s[i]-'a';
		if(!t[pos][x]) t[pos][x]=++tot;
		pos=t[pos][x];
	}
	ed[id]=pos;
}
vector<int> g[N];
void build(){
	queue<int> q;
	for(int i=0;i<26;i++) if(t[0][i]) q.push(t[0][i]);
	while(!q.empty()){
		int p=q.front();
		q.pop();
		g[fail[p]].push_back(p);
		for(int x=0;x<26;x++){
			if(t[p][x]) fail[t[p][x]]=t[fail[p]][x],q.push(t[p][x]);
			else t[p][x]=t[fail[p]][x];
		}
	}
}
int dfn[N],siz[N],dep[N],cnt,f[N][26];
void dfs(int x){
	dfn[x]=++cnt;
	siz[x]=1;
	f[x][0]=fail[x];
	dep[x]=dep[f[x][0]]+1;
	for(int i=1;i<=25;i++) f[x][i]=f[f[x][i-1]][i-1];
	for(auto y:g[x]) dfs(y),siz[x]+=siz[y];
}
int a[N],tp;
bool cmp(int x,int y){ return dfn[x]<dfn[y]; }
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=25;i>=0;i--)
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=25;i>=0;i--)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
int c[N];
int lowbit(int x){ return x&(-x); }
void add(int x,int v){ for(int i=x;i<=cnt;i+=lowbit(i)) c[i]+=v; }
void modify(char s[]){
	int n=strlen(s),pos=0;
	for(int i=0;i<n;i++){
		int x=s[i]-'a';
		pos=t[pos][x];
		a[++tp]=pos;
	}
	sort(a+1,a+tp+1);
	tp=unique(a+1,a+tp+1)-a-1;
	sort(a+1,a+tp+1,cmp);
	for(int i=1;i<=tp;i++) add(dfn[a[i]],1);
	for(int i=1;i<tp;i++) add(dfn[lca(a[i],a[i+1])],-1);
}
int query(int x){
	int res=0;
	for(int i=x;i;i-=lowbit(i)) res+=c[i];
	return res;
}
char s[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s);
		insert(s,i);
	}
	build();
	dfs(0);
	scanf("%d",&q);
	while(q--){
		int op;
		scanf("%d",&op);
		if(op==1){
			tp=0;
			scanf("%s",s);
			modify(s);
		}else{
			int x;
			scanf("%d",&x);
			int l=dfn[ed[x]],r=dfn[ed[x]]+siz[ed[x]]-1;
			printf("%d\n",query(r)-query(l-1));
		}
	}
	return 0;
}

F

考察了矩阵优化 dp 的一些定义,以及 kmp 算法。

首先考虑一个复杂度为 \(O(n \cdot m^2)\) 的 dp 做法。

\(dp_{i,j}\) 表示大串匹配到第 \(i\) 位,小串匹配到第 \(j\) 位的方案数。

为了使得小串不出现,那么最后的答案就是 \(\sum_{i=0}^{m-1} dp_{n,i}\)

考虑转移,假设第 \(i\) 位和第 \(j\) 位相等,那么就会转移到 \(dp_{i+1,j+1}\)

那如果不相等呢,在小串上跳,直到相等,得到一个串的长度为 \(k\) ,那么就转移到 \(dp_{i+1,k}\) ,怎么找 \(k\) 呢,我们发现这个跳的过程不就是 kmp 吗。

通过 kmp,预处理出一个 \(f\) 数组,\(f_{j,k}\) 表示在 \(j\) 状态上,增加一个字符到达 \(k\) 状态的方案数。

那么每次 dp 时转移就直接 \(dp_{i,j}=\sum_{k=0}^{m-1} dp_{i-1,k} \times f_{k,j}\)

至此,朴素 dp 的做法结束了。

我们用肉眼观察一下这个式子,发现可以套矩阵,于是直接优化就做完了。

\(40pts:\)

#include <bits/stdc++.h>
using namespace std;
const int N=25,M=2e5+5;
int n,m,mod;
char a[N];
int dp[M][N];
int f[N][N],nxt[N];
void add(int &x,int y){ x=(x+y)%mod; }
void kmp(){
	for(int i=2,j=0;i<=m;i++){
		while(j&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		nxt[i]=j;
	}
	for(int i=0;i<m;i++){
		for(int k=0;k<=9;k++){
			int j=i;
			while(j&&k+'0'!=a[j+1]) j=nxt[j];
			if(k+'0'==a[j+1]) j++;
			if(j<m) add(f[i][j],1);
		}
	}
}
int ans;
int main(){
	scanf("%d%d%d%s",&n,&m,&mod,a+1);
	kmp();
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			for(int k=0;k<m;k++){
				add(dp[i][j],dp[i-1][k]*f[k][j]%mod);
			}
		}
	}
	for(int i=0;i<m;i++) add(ans,dp[n][i]);
	printf("%d",ans);
	return 0;
} 

注意到矩阵不具有交换律,初始矩阵一定要在最前面乘,所以最后的答案应该是第一行,而不是第一列。

#include <bits/stdc++.h>
using namespace std;
const int N=25,M=2e5+5;
int n,m,mod;
char a[N];
int nxt[N];
struct ma{
	int x[N][N]; 
	ma(){ memset(x,0,sizeof x); }
}A;
void add(int &x,int y){ x=(x+y)%mod; }
void kmp(){
	for(int i=2,j=0;i<=m;i++){
		while(j&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		nxt[i]=j;
	}
	for(int i=0;i<m;i++){
		for(int k=0;k<=9;k++){
			int j=i;
			while(j&&k+'0'!=a[j+1]) j=nxt[j];
			if(k+'0'==a[j+1]) j++;
			if(j<m) add(A.x[i][j],1);
		}
	}
}
void mul(ma &m1,ma m2){
	ma m3;
	for(int i=0;i<m;i++)
		for(int j=0;j<m;j++)
			for(int k=0;k<m;k++)
				add(m3.x[i][j],m1.x[i][k]*m2.x[k][j]%mod);
	m1=m3;
}
ma qpow(ma a,int b){
	ma res;
	for(int i=0;i<m;i++) res.x[i][i]=1;
	while(b){
		if(b&1) mul(res,a);
		mul(a,a);
		b>>=1;
	}
	return res;
}
int ans;
int main(){
	scanf("%d%d%d%s",&n,&m,&mod,a+1);
	kmp();
	ma B=qpow(A,n);
	for(int i=0;i<m;i++) add(ans,B.x[0][i]);
	printf("%d",ans);
	return 0;
}

H

考察单调栈和单调队列的一道综合题目。

首先把删除变成加入,倒着操作使答案只增不减。

对于每一个点,我们可以预处理出它最上面和最下面能到的位置,对于修改,只有这一列有变化,暴力修改。

对于计算答案,只有这一行的答案会发生变化,用双指针套单调队列维护一下即可。

注意不要把 while 写成 if

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N=2e3+5;
struct node{ int x,y; }a[N]; 
int n,m,q,t;
char s[N][N];
int f[N][N],g[N][N];
void solve(int y){
	int p=0;
	for(int i=1;i<=n;i++){
		if(s[i][y]=='X') p=i;
		f[i][y]=i-p;
	}
	p=n+1;
	for(int i=n;i>=1;i--){
		if(s[i][y]=='X') p=i;
		g[i][y]=p-i;
	}
}
deque<int> q1,q2;
bool check(int x,int l,int r){
	int m1=f[x][r],m2=g[x][r];
	if(!q1.empty()) m1=min(m1,f[x][q1.front()]);
	if(!q2.empty()) m2=min(m2,g[x][q2.front()]);
	return m1+m2>=r-l+2;
}
void add(int x,int y){
	while(!q1.empty()&&f[x][y]<=f[x][q1.back()]) q1.pop_back();
	q1.push_back(y);
	while(!q2.empty()&&g[x][y]<=g[x][q2.back()]) q2.pop_back();
	q2.push_back(y);	
}
int getans(int x){
	q1.clear(),q2.clear();
	int res=0;
	for(int l=1,r=0;l<=m;l++){
		if(r<l-1) r=l-1;
		while(r<m&&check(x,l,r+1)) r++,add(x,r);
		res=max(res,r-l+1);
		if(!q1.empty()&&q1.front()==l) q1.pop_front();
		if(!q2.empty()&&q2.front()==l) q2.pop_front();
	}
	return res;
}
int ans[N];
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&a[i].x,&a[i].y);
		s[a[i].x][a[i].y]='X';
	}
	for(int i=1;i<=m;i++) solve(i);
	for(int i=1;i<=n;i++) t=max(t,getans(i));
	for(int i=q;i>=1;i--){
		ans[i]=t;
		s[a[i].x][a[i].y]='.';
		solve(a[i].y);
		t=max(t,getans(a[i].x));
	}
	for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2023-09-11 18:00  syta  阅读(231)  评论(4)    收藏  举报