梦熊2026青岛C班笔记

开坑7.19才开始
就不写详细的题解了,因为我来不及写完
只会写出很大概的思路以及留下我的代码
对了,以前没写过的题可能会写得稍微详细一些些,不用抱太大期望

Day1 7.19 树状数组&线段树

简单回顾了一下树状数组以及线段树,模版题除非我没学过否则就不给出来了

AT_dp_q Flowers

看题我们有一个很朴素的 \(\mathcal{O(n^2)}\) 的动态规划然后使用树状数组优化这个动态规划即可

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,h[N];
using ll = long long;
ll a[N],c[N],dp[N],ans;
#define lowbit(x) ((x)&-(x))
void add(ll x,int i){
	for(;i<=n;i+=lowbit(i))
		c[i]=max(c[i],x);
}
ll query(int i){
	ll re=0;
	for(;i;i-=lowbit(i))
		re=max(re,c[i]);
	return re;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&h[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=n;i++){
		dp[i]=a[i]+query(h[i]-1);
		add(dp[i],h[i]);
		ans=max(ans,dp[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

小白逛公园

区间最大子段和+单点修改,懒得写了,代码用的以前的

#include<bits/stdc++.h>
using namespace std;
#define ls (u<<1)
#define rs (u<<1|1)
const int N=5e5+5;
struct SegmentTree{
	int sum,lsum,rsum,ssum,l,r;
}tr[N<<2];
int n,m,a[N];
void pushup(int u){
	tr[u].sum=tr[ls].sum+tr[rs].sum;
	tr[u].lsum=max(tr[rs].lsum+tr[ls].sum,tr[ls].lsum);
	tr[u].rsum=max(tr[ls].rsum+tr[rs].sum,tr[rs].rsum);
	tr[u].ssum=max(tr[ls].ssum,max(tr[rs].ssum,tr[ls].rsum+tr[rs].lsum));
}
void build(int u,int l,int r){
	tr[u].l=l;tr[u].r=r;
	if(l==r){
		tr[u].sum=tr[u].lsum=tr[u].rsum=tr[u].ssum=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(u);
}
SegmentTree query(int u,int l,int r,int x,int y){
	if(x<=l&&r<=y)return tr[u];
	int mid=(tr[u].l+tr[u].r)>>1;
	if(y<=mid) return query(ls,l,mid,x,y);
	if(x>mid) return query(rs,mid+1,r,x,y);
	SegmentTree ans,la=query(ls,l,mid,x,y),ra=query(rs,mid+1,r,x,y);
	ans.sum=la.sum+ra.sum;
	ans.lsum=max(la.sum+ra.lsum,la.lsum);
	ans.rsum=max(la.rsum+ra.sum,ra.rsum);
	ans.ssum=max(la.ssum,max(ra.ssum,la.rsum+ra.lsum));
	return ans;
}
void change(int u,int x,int k){
	if(tr[u].l==tr[u].r){
		tr[u].sum=tr[u].lsum=tr[u].rsum=tr[u].ssum=k;
		return ;
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(x<=mid)change(ls,x,k);
	else change(rs,x,k);
	pushup(u);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n);
	while(m--){
		int op,x,y;
		cin>>op>>x>>y;
		switch(op){
		case 1:{
			if(x>y)swap(x,y);
			SegmentTree s=query(1,1,n,x,y);
			cout<<s.ssum<<'\n';
			break;
		}
		case 2:change(1,x,y);break;
		}
	}
	return 0;
}

上帝造题的七分钟 2 / 花神游历各国

区间修改+区间查询求和
注意到题目没有对元素的增加操作,而每次开根号都是 \(\leq\) 原来的值的,而又由于题目中 \(a_i \leq 10^{12}\) 所以开不了几次根。只需要记录一个区间最大值maxx,当 maxx==1 时就可以不管了,其他时候 \(\mathcal{O(n logn)}\) 直接改即可

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
using ll = long long;
struct sgt{
	int l,r;
	ll sum,maxx;
}tr[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
int n,m;
ll a[N];
void pushup(int u){
	tr[u].sum=(tr[ls].sum+tr[rs].sum);
	tr[u].maxx=max(tr[ls].maxx,tr[rs].maxx);
}
void build(int u,int l,int r){
	tr[u].l=l;tr[u].r=r;
	if(l==r){
		tr[u].maxx=tr[u].sum=a[l];return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(u);
}
void change(int u,int x,int y){
	if(tr[u].l>y||tr[u].r<x||tr[u].maxx==1)return ;
	if(tr[u].l==tr[u].r){
		tr[u].sum=sqrt(tr[u].sum);
		tr[u].maxx=tr[u].sum;
		return ;
	}
	change(ls,x,y);change(rs,x,y);
	pushup(u);
}
ll query(int u,int x,int y){
	if(tr[u].l>y||tr[u].r<x)return 0;
	if(tr[u].l>=x&&tr[u].r<=y)return tr[u].sum;
	return query(ls,x,y)+query(rs,x,y);
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	build(1,1,n);
	scanf("%d",&m);
	while(m--){
		int op,x,y;ll k;
		scanf("%d",&op);
		if(op==0){
			scanf("%d%d",&x,&y);
			if(x>y)swap(x,y);
			change(1,x,y);
		}
		else{
			scanf("%d%d",&x,&y);
			if(x>y)swap(x,y);
			printf("%lld\n",query(1,x,y));
		}
	}
	return 0;
}

窗口的星星

扫描线+转化
把矩形窗户转化为对于每个星星,能包含它的矩形,然后使用扫描线求最大值即可

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int t,n,m,w,h,ys[N<<1];
using ll = long long;
const ll INF=1e18;
struct matrix{
	ll x,y1,y2,k;
	bool operator <(const matrix& b) const{
		return x==b.x? k<b.k:x<b.x;
	}
}a[N<<1];
ll ans;
struct sgt{
	ll tag,maxx;
}tr[N<<3];
#define ls (u<<1)
#define rs (u<<1|1)
void pushup(int u){
	tr[u].maxx=max(tr[ls].maxx,tr[rs].maxx);
}
void build(int u,int l,int r){
	tr[u].maxx=tr[u].tag=0;
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(u);
}
void pushdown(int u,int l,int r){
	if(tr[u].tag){
		tr[ls].tag+=tr[u].tag;
		tr[ls].maxx+=tr[u].tag;
		tr[rs].tag+=tr[u].tag;
		tr[rs].maxx+=tr[u].tag;
		tr[u].tag=0;
	}
}
void add(int u,int l,int r,int x,int y,ll k){
	if(l>y||r<x)return ;
	if(l>=x&&r<=y){
		tr[u].maxx+=k;
		tr[u].tag+=k;
		return ;
	}
	pushdown(u,l,r);
	int mid=(l+r)>>1;
	add(ls,l,mid,x,y,k);
	add(rs,mid+1,r,x,y,k);
	pushup(u);
}
ll query(int u,int l,int r,int x,int y){
	if(l>x||r<y)return -INF;
	if(l>=x&&r<=y)return tr[u].maxx;
	pushdown(u,l,r);
	int mid=(l+r)>>1;
	return max(query(ls,l,mid,x,y),query(rs,mid+1,r,x,y));
}

int main(){
	scanf("%d",&t);
	while(t--){
		ans=0;
		scanf("%d%d%d",&n,&w,&h);
		for(int i=1;i<=n;i++){
			ll x,y,l;scanf("%lld%lld%lld",&x,&y,&l);
			int o=i<<1;
			a[o]=(matrix){x,y,y+h-1,l};
			a[o-1]=(matrix){x+w,y,y+h-1,-l};
			ys[o]=y+h-1;ys[o-1]=y;
		}
		sort(ys+1,ys+(n<<1)+1);
		m=unique(ys+1,ys+(n<<1)+1)-ys-1;
		sort(a+1,a+(n<<1)+1);
		for(int i=1;i<=(n<<1);i++){
			a[i].y1=lower_bound(ys+1,ys+m+1,a[i].y1)-ys;
			a[i].y2=lower_bound(ys+1,ys+m+1,a[i].y2)-ys;
		}
		build(1,1,m);
		for(int i=1;i<=(n<<1);i++){
			add(1,1,m,a[i].y1,a[i].y2,a[i].k);
			ans=max(ans,query(1,1,m,1,m));
		}
		printf("%lld\n",ans);
	}
	return 0;
}

矩形周长并

这里采用的是扫描两次分为左->右和上->下然后分别把答案加起来,注意维护的时候只能维护区间的一段而不能是区间的“点数”

#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int n,ys[N<<1],xs[N<<1],m1,m2;
using ll = long long;
ll ans;
struct matrix{
	int x,y1,y2,k;
	bool operator <(const matrix& b) const {
		return x==b.x? k>b.k:x<b.x;
	}
}a[N<<1],b[N<<1];

struct sgt{
	int _l[N<<3],_r[N<<3],cnt[N<<3],len[N<<3];
	#define ls (u<<1)
	#define rs (u<<1|1)
	void update(int u,int w[]){
		if(cnt[u])len[u]=w[_r[u]+1]-w[_l[u]];
		else len[u]=len[ls]+len[rs];
	}
	void build(int u,int l,int r){
		_l[u]=l;_r[u]=r;cnt[u]=0;len[u]=0;
		if(l==r)return ;
		int mid=(l+r)>>1;
		build(ls,l,mid);build(rs,mid+1,r);
	}
	void add(int u,int x,int y,int k,int w[]){
		if(_l[u]>y||_r[u]<x)return ;
		if(_l[u]>=x&&_r[u]<=y){
			cnt[u]+=k;
			update(u,w);
			return ;
		}
		add(ls,x,y,k,w);add(rs,x,y,k,w);
		update(u,w);
	}
}tr1,tr2;


int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x1,x2,y1,y2;
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		int o=i<<1;
		a[o]=(matrix){x1,y1,y2,1};a[o-1]=(matrix){x2,y1,y2,-1};
		b[o]=(matrix){y1,x1,x2,1};b[o-1]=(matrix){y2,x1,x2,-1};
		ys[o]=y1;ys[o-1]=y2;
		xs[o]=x1;xs[o-1]=x2;
	}
	sort(a+1,a+(n<<1)+1);sort(b+1,b+(n<<1)+1);
	sort(ys+1,ys+(n<<1)+1);sort(xs+1,xs+(n<<1)+1);
	m1=unique(ys+1,ys+(n<<1)+1)-ys-1;m2=unique(xs+1,xs+(n<<1)+1)-xs-1;
	for(int i=1;i<=(n<<1);i++){
		a[i].y1=lower_bound(ys+1,ys+m1+1,a[i].y1)-ys;a[i].y2=lower_bound(ys+1,ys+m1+1,a[i].y2)-ys;
		b[i].y1=lower_bound(xs+1,xs+m2+1,b[i].y1)-xs;b[i].y2=lower_bound(xs+1,xs+m2+1,b[i].y2)-xs;
	}
	tr1.build(1,1,m1-1);tr2.build(1,1,m2-1);
	int lastxlen=0,lastylen=0;
	for(int i=1;i<=(n<<1);i++){
		tr1.add(1,a[i].y1,a[i].y2-1,a[i].k,ys);tr2.add(1,b[i].y1,b[i].y2-1,b[i].k,xs);
		ans+=abs(tr1.len[1]-lastylen)+abs(tr2.len[1]-lastxlen);
		lastylen=tr1.len[1];lastxlen=tr2.len[1];
	}
	printf("%lld\n",ans);
	
	return 0;
}

AT_abc287_g Balance Update Query

在权值线段树上二分求前 \(k\) 个(感觉好像平衡树怎么说)
懒得写离散化了,所以写的动态开点

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int V=1e9;
int n,tot,rt,q;
using ll = long long;
ll a[N],b[N];
struct wsgt{
	int ls,rs;
	ll cnt,sum;
}tr[N*120];
void addnew(int& u){
	u=++tot;
	tr[u].ls=tr[u].rs=tr[u].cnt=tr[u].sum=0;
}
void add(int& u,int l,int r,int pos,ll k){
	if(!u)addnew(u);
	tr[u].cnt+=k;tr[u].sum+=pos*k;
	if(l==r)return ;
	int mid=(l+r)>>1;
	if(pos<=mid) add(tr[u].ls,l,mid,pos,k);
	else add(tr[u].rs,mid+1,r,pos,k);
}
ll solve(int u,int l,int r,ll k){
	if(!u||k<=0)return 0;
	if(l==r)return k*l;
	int mid=(l+r)>>1;
	int w=tr[u].rs? tr[tr[u].rs].cnt:0;
	if(w>=k)
		return solve(tr[u].rs,mid+1,r,k);
	else return solve(tr[u].ls,l,mid,k-w)+(tr[u].rs? tr[tr[u].rs].sum:0);
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&a[i],&b[i]);
		add(rt,0,V,a[i],b[i]);
	}
	scanf("%d",&q);
	while(q--){
		int op;ll x,y;scanf("%d",&op);
		if(op==1){
			scanf("%lld%lld",&x,&y);
			add(rt,0,V,a[x],-b[x]);
			a[x]=y;
			add(rt,0,V,a[x],b[x]);
		}
		else if(op==2){
			scanf("%lld%lld",&x,&y);
			add(rt,0,V,a[x],-b[x]);
			add(rt,0,V,a[x],y);
			b[x]=y;
		}
		else{
			scanf("%lld",&x);
			if(tr[rt].cnt<x)printf("-1\n");
			else printf("%lld\n",solve(rt,0,V,x));
		}
	}
	return 0;
}

[省选联考 2020 A/B 卷] 冰火战士

待补

算术天才⑨与等差数列

待补

Day 2 模拟赛

不是我还以为是线段树专题结果有0道题是线段树

T1 CF1082C. Multi-Subject Competition

简单题

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
using ll = long long;
ll sum[N],ans;
vector<ll> g[N];
bool cmp(int a,int b){
	return a>b;
}

int main(){
	freopen("subject.in","r",stdin);
	freopen("subject.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		int s,r;scanf("%d%d",&s,&r);
		g[s].push_back(r);
	}
	for(int i=1;i<=m;i++){
		if(g[i].size()){
			sort(g[i].begin(),g[i].end(),cmp);
		}
	}
	for(int i=1;i<=m;i++){
		int t=g[i].size();
		if(!t)continue;
		for(int j=0;j<t;j++){
			if(j>0){g[i][j]+=g[i][j-1];}
		}
		for(int j=0;j<t;j++){
			if(g[i][j]>0){
				sum[j]+=g[i][j];
			}
		}
	}
	for(int i=0;i<=m;i++)ans=max(ans,sum[i]);
	printf("%lld\n",ans);
	return 0;
}

T2 P14154 [ICPC 2022 Nanjing R] 索道

单调队列划优化 \(dp\) 修改时我们可以考虑影响范围,然后使用类似夹b法来解决修改的问题复杂度 \(\mathcal{O(n+kq)}\) 恰好通过本题

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
char s[N];
using ll = long long;
const ll INF=1e18;
int t,n,k,q,cnt,cut[N],num[N];
int pre[N],nxt[N];
int q1[N],head1,tail1,q2[N],head2,tail2;
ll f[N],a[N],g[N];

inline int read(){
	int x=0,t=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return  x*t;
}

int main(){
	t=read();
	while(t--){
		n=read();k=read();
		for(int i=1;i<=n;i++)a[i]=read();
		scanf("%s",s+1);
		for(int i=1;i<=n;i++)
			if(s[i]=='1')pre[i]=i;
			else pre[i]=pre[i-1];
		nxt[n+1]=n+1;
		for(int i=n;i>=1;i--)
			if(s[i]=='1')nxt[i]=i;
			else nxt[i]=nxt[i+1];
		head1=1;tail1=1;q1[head1]=0;
		for(int i=1;i<=n+1;i++){
			while(head1<=tail1&&i-q1[head1]>k)head1++;
			f[i]=f[q1[head1]]+a[i];
			while(head1<=tail1&&f[q1[tail1]]>=f[i])tail1--;
			if(s[i]=='1'){head1=1;tail1=1;q1[head1]=i;}
			else q1[++tail1]=i;
		}
		head2=1;tail2=1;q2[head2]=n+1;
		for(int i=n;i>=1;i--){
			while(head2<=tail2&&q2[head2]-i>k)head2++;
			g[i]=g[q2[head2]]+a[i];
			while(head2<=tail2&&g[q2[tail2]]>=g[i])tail2--;
			if(s[i]=='1'){head2=1;tail2=1;q2[head2]=i;}
			else q2[++tail2]=i;
		}
		q=read();
		while(q--){
			int p=read(),v=read();
			if(s[p]=='1'){
				printf("%lld\n",f[n+1]+v-a[p]);
			}
			else{//请务必加深理解此处的含义
				ll w=f[p]+g[p]-a[p]*2+v;//第p个地方修建时的答案
				ll z=INF;
				for(int i=p-k+1;i<pre[p];i++){//看看有没有其他地方能组成更小的答案的
					if(i+k<=nxt[p]) z=min(z,g[i+k]);
				}
				for(int i=max(pre[p],p-k+1);i<p;i++){//同上
					if(i+k<=nxt[p]) z=min(z,g[i+k]);
					w=min(w,f[i]+z);
				}
				printf("%lld\n",w);
			}
		}
		for(int i=0;i<=n+1;i++)f[i]=g[i]=0;
		for(int i=0;i<=n+1;i++)a[i]=0;
	}
	
	return 0;
}

T3 QOJ7749

没讲但是我也不会做

T4

难爆了

待补

然后我们又回到了线段树&树状数组

U1384 Propagating tree

U1380 Nezzar and Binary String

P4246 [SHOI2008] 堵塞的交通

李超线段树

这特么没学过啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊.......(恼)

P4097 【模板】李超线段树 / [HEOI2013] Segment

好的,这是模板题。

P4254 [JSOI2008] Blue Mary 开公司

模板题,基本没啥变化又水一道题

P4655 [CEOI 2017] Building Bridges

斜率优化+李超线段树,意料之中的组合。

Day3 又是模拟赛 233

昨天晚上写了inf道题,今天又要写9.178道233
虽然我们今天打了模拟赛,但是我还是拉完了233

T1 AT_abc182_e [ABC182E] Akari

考的时候糖丸了,没处理边界,这种上下左右覆盖的可以遇到同类就停下,因为同类作用是一样的。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
const int M=1e5+5;
const int W=1505;
const int INF=1e9;
int h,w,n,m,ans;
struct ball{
	int a,b;
}a[N],b[N];
int blk[W][W],bl[W][W];
int cnt[W][W];

int main(){
//	freopen("grid.in","r",stdin);
//	freopen("grid.out","w",stdout);
	scanf("%d%d%d%d",&h,&w,&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i].a,&a[i].b);
		bl[a[i].a][a[i].b]=1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&b[i].a,&b[i].b);
		bl[b[i].a][b[i].b]=-1;
	}
	for(int i=1;i<=n;i++){
		int x=a[i].a,y=a[i].b;
		cnt[x][y]=1;
		for(int j=x+1;j<=h;j++){
			if(bl[j][y]==-1)break;
			cnt[j][y]=1;
			if(bl[j][y]==1)break;
		}
		for(int j=x-1;j>=1;j--){
			if(bl[j][y]==-1)break;
			cnt[j][y]=1;
			if(bl[j][y]==1)break;
		}
		for(int j=y+1;j<=w;j++){
			if(bl[x][j]==-1)break;
			cnt[x][j]=1;
			if(bl[x][j]==1)break;
		}
		for(int j=y-1;j>=1;j--){
			if(bl[x][j]==-1)break;
			cnt[x][j]=1;
			if(bl[x][j]==1)break;
		}
	}
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(cnt[i][j]>0)++ans;
		}
	}
	printf("%d\n",ans);
	
	return 0;
}

T2 QOJ#7937. Fast XORting

赛时根本不会做,赛后卫生设备,妈的调个题能把我搞死

#include<bits/stdc++.h>
using namespace std;
const int N=262144+5;
#define ll long long
int n,a[N],tmp[N];
ll c[N],ans;

#define lowbit(x) ((x)&-(x))
void add(int i,ll x){
	for(;i<=n;i+=lowbit(i))c[i]+=x;
}
ll ask(int i){
	int re=0;
	for(;i;i-=lowbit(i))re+=c[i];
	return re;
}

ll calc(int a[]){
	memset(c,0,sizeof c);
	ll ans=0;
	for(int i=n;i>=1;i--){
		ans+=ask(a[i]);
		add(a[i]+1,1);
	}
	return ans;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	int t=log2(n);
	int x=0;
	ans=calc(a);
	for(int i=t;i>=1;i--){
		int y=x|(1<<(i-1));
		for(int j=1;j<=n;j++)tmp[j]=a[j]^y;
		ll tans=calc(tmp);
		if(tans<ans){
			x=y;
			ans=tans;
		}
	}
	if(x!=0)ans++;
	printf("%lld\n",ans);
	
	return 0;
}

T3

T4

下午是树上问题

CF1328E Tree Queries

CF1304E 1-Trees and Queries

P2680 运输计划

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,f[N],p[N][32],dep[N],t,u[N],v[N],dfn[N],ddd,siz[N];
using ll = long long;
ll T[N],maxt,d[N];
vector<pair<int,int>> g[N];
#define mkp(x,y) make_pair((x),(y))

inline int read(){
	int x=0,t=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*t;
}

inline void dfs(int u,int fa){
	siz[u]=1;
	dfn[++ddd]=u;
	dep[u]=dep[fa]+1;
	p[u][0]=fa;
	for(int i=1;(1<<i)<=dep[u];i++)
		p[u][i]=p[p[u][i-1]][i-1];
	for(auto to:g[u]){
		int v=to.first;ll w=to.second;
		if(v==fa)continue;
		d[v]=d[u]+w;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}
inline int lca(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	for(int i=t;i>=0;i--)
		if(dep[u]+(1<<i)<=dep[v])v=p[v][i];
	if(u==v)return u;
	for(int i=t;i>=0;i--)
		if(p[u][i]!=p[v][i])
			u=p[u][i],v=p[v][i];
	return p[u][0];
}

//inline void calc(int u,int fa,int cnt,ll &ans){
//	for(auto t:g[u]){
//		int v=t.first;ll w=t.second;
//		if(v==fa)continue;
//		calc(v,u,cnt,ans);
//		f[u]+=f[v];
//		if(f[v]==cnt){
//			ans=max(ans,w);
//		}
//	}
//}
//上面会TLE
inline void calc(int cnt,ll &ans){
	for(int i=n;i>=1;i--){
		int u=dfn[i];
		for(auto to:g[u]){
			int v=to.first;ll w=to.second;
			if(v==p[u][0])continue;
			f[u]+=f[v];
			if(f[v]==cnt)ans=max(ans,w);
		}
	}
}
//前序遍历倒过来就是后序遍历了

inline bool check(int mid){
	memset(f,0,sizeof f);
	vector<int> e;
	for(int i=1;i<=m;i++){
		if(T[i]>mid)e.push_back(i);
	}
	ll cnt=e.size();
	for(auto i:e){
		f[u[i]]++;f[v[i]]++;
		f[lca(u[i],v[i])]-=2;
	}
	ll tans=0;
	calc(cnt,tans);
	return maxt-tans<=mid;
}

int main(){
	n=read();m=read();t=log2(n);
	for(int i=1;i<n;i++){
		int u=read(),v=read(),w=read();
		g[u].push_back(mkp(v,w));g[v].push_back(mkp(u,w));
	}
	dfs(1,0);
	for(int i=1;i<=m;i++){
		u[i]=read();v[i]=read();
		T[i]=d[u[i]]+d[v[i]]-(d[lca(u[i],v[i])]<<1);
		maxt=max(maxt,T[i]);
	}
	int l=0,r=maxt,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	
	return 0;
}

然而还有启发式合并

XOR Tree

Day4 字符串专题

P5546 [POI 2000 R3] 公共串

很暴力地去求然后比对即可 本题稍微卡hash的模数

#include<bits/stdc++.h>
using namespace std;
const int N=10;
const int L=2005;
const int mod=998244353;
const int P=1331;
string s[N];
using ll = long long;
int sum[N][L],n,m,p[L];

int calc(int i,int l,int r){
	if(l==0)return sum[i][r];
	return ((sum[i][r]-1ll*p[r-l+1]*sum[i][l-1])%mod+mod)%mod;
}

bool check(int mid){
	vector<pair<int,int>> q;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(int)s[i].size()-mid;j++){
			int t=calc(i,j,j+mid);
			q.push_back(make_pair(t,i));
		}
	}
	sort(q.begin(),q.end());
	for(int i=0;i<(int)q.size();){
		int t=q[i].first,cnt[N]={0};
		while(q[i].first==t&&i<(int)q.size()){
			cnt[q[i].second]++;
			i++;
		}
		int num=0;
		for(int j=1;j<=n;j++){
			if(cnt[j]>0)num++;
		}
		if(num==n)return 1;
	}
	return 0;
}

int main(){
	scanf("%d",&n);
	p[0]=1;
	for(int i=1;i<=2000;i++)
		p[i]=p[i-1]*1ll*P%mod;
	for(int i=1;i<=n;i++){cin>>s[i];}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=(int)s[i].size();j++){
			if(j>0)sum[i][j]=1ll*sum[i][j-1]*P%mod;
			sum[i][j]=(s[i][j]+sum[i][j])%mod;
		}
	}
	int l=0,r=2000,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid))l=mid+1;
		else r=mid;
	}
	printf("%d\n",l);
	
	return 0;
}

前缀统计

用的以前的代码

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=3e6+5;
char s1[maxn];
int t,p,ed[maxn],trie[maxn][65],tot;
int numm(char c){
	if('A'<=c&&c<='Z')return c-'A';
	else if('a'<=c&&c<='z')return c-'a'+26;
	else return c-'0'+52;
}
void insert(char s[]){
	int len=strlen(s),p=0;
	for(int i=0;i<len;i++){
		int ch=numm(s[i]);
		if(!trie[p][ch])trie[p][ch]=++tot;
		p=trie[p][ch];
	}
	ed[p]++;
}
int find(char s[]){
	int p=0,len=strlen(s),ans=0;
	for(int i=0;i<len;i++){
		int ch=numm(s[i]);
		if(!trie[p][ch])return ans;
		p=trie[p][ch];
		ans+=ed[p];
	}
	return ans;
}

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	int n,m;
	cin>>n>>m;
	tot=0;
	while(n--){
		cin>>s1;
		insert(s1);
	}
	while(m--){
		cin>>s1;
		cout<<find(s1)<<'\n';
	}

	return 0;
}

最大异或

这个也用的以前的代码

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1e7 + 5;
int n, trie[maxn][2], tot, a[maxn];
long long ans,sum;

void insert(int x) {
	int p = 0;
	for (int i = 31; i >= 0; i--) {
		int ch = (x >> i) & 1;
		if (!trie[p][ch])trie[p][ch] = ++tot;
		p = trie[p][ch];
	}
}
void find(int x) {
	ans = 0;
	int p = 0;
	for (int i = 31; i >= 0; i--) {
		int ch = (x >> i) & 1;
		if (trie[p][ch^1]) {
			ans += (1 << i);
			p=trie[p][ch^1];
		}
		else p = trie[p][ch];
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		find(a[i]);
		insert(a[i]);
		sum = max(sum, ans);
	}
	printf("%lld\n", sum);
	return 0;
}

P4551 最长异或路径

可以把边权转化为点权然后就和上道题一样了,代码用的以前的

#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1000000 + 5;
int n, trie[maxn][2], tot,d[maxn];
long long ans, sum;
vector<pair<int,int>> g[maxn];
	
void insert(int x) {
	int p = 0;
	for (int i = 31; i >= 0; i--) {
		int ch = (x >> i) & 1;
		if (!trie[p][ch])trie[p][ch] = ++tot;
		p = trie[p][ch];
	}
}
void find(int x) {
	ans = 0;
	int p = 0;
	for (int i = 31; i >= 0; i--) {
		int ch = (x >> i) & 1;
		if (trie[p][ch ^ 1]) {
			ans += (1 << i);
			p = trie[p][ch ^ 1];
		} else p = trie[p][ch];
	}
}
void dfs(int u,int fa){
	for(int i=0;i<(int)g[u].size();i++){
		if(g[u][i].first==fa)continue;
		d[g[u][i].first]=d[u]^g[u][i].second;
		dfs(g[u][i].first,u);
	}
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		g[u].push_back(make_pair(v,w));
		g[v].push_back(make_pair(u,w));
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)insert(d[i]);
	for (int i = 1; i <= n; i++) {
		find(d[i]);
		sum = max(sum, ans);
	}
	printf("%lld\n", sum);
	return 0;
}   
	

P3065 [USACO12DEC] First! G

trie+拓扑排序判断环

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,trans[N][26],cnt,in[30],ed[N],tot;
vector<int> g[30];
queue<int> q;
string s[N],ans[N];

void insert(string s){
	int x=s.size(),cur=0;
	for(int i=0;i<x;i++){
		if(!trans[cur][s[i]-'a'])trans[cur][s[i]-'a']=++cnt;
		cur=trans[cur][s[i]-'a'];
	}
	ed[cur]=1;
}

bool find(string s){
	int x=s.size(),cur=0;
	memset(in,0,sizeof in);
	for(int i=0;i<26;i++)if(g[i].size())g[i].clear();
	for(int i=0;i<x;i++){
		int t=s[i]-'a';
		if(ed[cur])return 0;
		for(int j=0;j<26;j++) {
			if(j!=t&&trans[cur][j]){
				g[t].push_back(j);
				++in[j];
			}
		}
		cur=trans[cur][t];
	}
	for(int i=0;i<26;i++){
		if(!in[i])q.push(i);
	}
	int num=0;
	while(!q.empty()){
		int u=q.front();q.pop();++num;
		for(auto v:g[u]){
			--in[v];
			if(in[v]==0)q.push(v);
		}
	}
	return num==26;
}

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		insert(s[i]);
	}
	for(int i=1;i<=n;i++){
		if(find(s[i]))ans[++tot]=s[i];
	}
	cout<<tot<<'\n';
	for(int i=1;i<=tot;i++)
		cout<<ans[i]<<'\n';
	return 0;
}

P5829 【模板】失配树

之前没做过

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,fail[N],m,p[N][32],dep[N],t;
char s[N];

int lca(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	for(int i=t;i>=0;i--)
		if(dep[u]+(1<<i)<=dep[v])v=p[v][i];
	if(u==v)return u;
	for(int i=t;i>=0;i--)
		if(p[u][i]!=p[v][i])u=p[u][i],v=p[v][i];
	return p[u][0];
}

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>(s+1);n=strlen(s+1);t=log2(n);
	dep[1]=1;
	for(int i=2,j=0;i<=n;i++){//此处构建fail树
		while(j>0&&s[i]!=s[j+1])j=fail[j];
		if(s[i]==s[j+1])j++;
		fail[i]=j;p[i][0]=j;dep[i]=dep[j]+1;
	}
	for(int j=1;j<=t;j++){
		for(int i=1;i<=n;i++)
			p[i][j]=p[p[i][j-1]][j-1];
	}
	cin>>m;
	while(m--){
		int P,Q;cin>>P>>Q;
		int l=lca(P,Q);
		if(l==P||l==Q)l=p[l][0];
		cout<<l<<'\n';
	}
	return 0;
}

P3121 [USACO15FEB] Censoring G

用一个stack维护一下剩下的单词即可

#include<bits/stdc++.h>//那时候我还很爱写 const int maxn
using namespace std;
const int maxn=1e5+5;
char s[maxn],t[maxn];
int n,fail[maxn],val[maxn],trans[maxn][26],len[maxn],tot,top,stk1[maxn],stk2[maxn],now;
vector<int> e[maxn];
void add(int rt,char *s){
    if(*s==0){
        val[rt]=strlen(t);
        return;
    }
    if(!trans[rt][*s-'a']) trans[rt][*s-'a']=++tot;
    add(trans[rt][*s-'a'],s+1);
}
void bfs(){
    queue<int> q;
    for(int i=0;i<26;i++){
        if(trans[0][i]) q.push(trans[0][i]);
    }
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=0;i<26;i++){
            int v=trans[u][i];
            if(!v){
                trans[u][i]=trans[fail[u]][i];
            }
            else{
                fail[v]=trans[fail[u]][i];
                q.push(v);
            }
        }
    }
}
int main(){
    scanf("%s%d",s,&n);
    for(int i=1;i<=n;i++){
        scanf("%s",t);
        add(0,t);
    }
    bfs();
    int len=strlen(s);
    top=0;
    now=0;
    for(int i=0;i<len;i++){
        now=trans[now][s[i]-'a'];
        stk1[++top]=now;
        stk2[top]=i;
        if(val[now]){
            top-=val[now];
            if(top<0) top=0;
            now=top?stk1[top]:0;
        }
    }
    for(int i=1;i<=top;i++){
        putchar(s[stk2[i]]);
    }
    putchar('\n');
    return 0;
}

P3041 [USACO12JAN] Video Game G

AC自动机上进行 \(dp\). 定义 $ f[i][u] $ 表示到trie树上的 \(u\) 这个点,目前字符串的长度为 \(i\) 的答案那么我们有 \(\mathcal{f[i+1][v]=\max\limits_{ v\in son(u) } (f[i+1][v],f[i][u]+val[v])}\) 其中 val[v] 是所有能匹配的个数最后答案就是 \(\max\limits_{1\le i \le N}f[k][i]\) 其中 \(N\)trie 的总结点数

#include<bits/stdc++.h>
using namespace std;
const int N=25;
const int L=20;
const int M=3e4+5;
const int K=1e3+5;
int n,k,cnt,trans[M][5],fail[M],val[M],f[K][M],ans;
string s[N];
vector<int> g[M];

void insert(string s){
	int x=s.size(),cur=0;
	for(int i=0;i<x;i++){
		if(!trans[cur][s[i]-'A'])trans[cur][s[i]-'A']=++cnt;
		cur=trans[cur][s[i]-'A'];
	}
	val[cur]++;
}
void build(){
	queue<int> q;
	for(int i=0;i<=2;i++)
		if(trans[0][i])q.push(trans[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		val[u]+=val[fail[u]];g[fail[u]].push_back(u);
		for(int i=0;i<=2;i++){
			int &v=trans[u][i];
			if(v){
				fail[v]=trans[fail[u]][i];
				q.push(v);
			}
			else v=trans[fail[u]][i];
		}
	}
}
int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>s[i];insert(s[i]);
	}
	build();
	memset(f,0xc0,sizeof f);
	f[0][0]=0;
	for(int i=0;i<k;i++){
		for(int u=0;u<=cnt;u++){
			for(int c=0;c<3;c++){
				f[i+1][trans[u][c]]=max(f[i+1][trans[u][c]],f[i][u]+val[trans[u][c]]);
			}
		}
	}
	for(int i=0;i<=cnt;i++)ans=max(ans,f[k][i]);
	cout<<ans<<'\n';
	return 0;
}

CF455B A Lot of Games

trie+动态规划+巴巴博弈

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,k,cnt,trans[N][26],ed[N],f[N],g[N];// f胜利 g输掉
string s;

void insert(string s){
	int x=s.size(),cur=0;
	for(int i=0;i<x;i++){
		if(!trans[cur][s[i]-'a'])trans[cur][s[i]-'a']=++cnt;
		cur=trans[cur][s[i]-'a'];
	}
	ed[cur]++;
}

void dp(int cur){
	bool leaf=1;
	for(int i=0;i<26;i++){
		if(!trans[cur][i])continue;
		dp(trans[cur][i]);
		if(!f[trans[cur][i]])f[cur]=1;
		if(!g[trans[cur][i]])g[cur]=1;
		leaf=0;
	}
	if(leaf){
		f[cur]=0;g[cur]=1;
	}
}

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>s;insert(s);
	}
	dp(0);
	if(f[0]==0) printf("Second\n");
	else if(f[0]==1&&g[0]==1)printf("First\n");
	else{
		if(k&1)printf("First\n");
		else printf("Second\n");
	}
	
	return 0;
}

Day5 动态规划专题

啊啊啊啊啊啊啊啊是动态规划啊啊啊啊啊啊啊啊

[HAOI2015] 树上染色

树上背包

#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int n,k,siz[N];
using ll = long long;
const ll INF=1e18;
ll f[N][N];
vector<pair<int,int>> g[N];
#define mkp(x,y) make_pair((x),(y))
#define fi first 
#define se second

void dfs(int u,int fa){
	siz[u]=1;f[u][0]=f[u][1]=0;
	for(auto to:g[u]){
		int v=to.fi,w=to.se;
		if(v==fa)continue;
		dfs(v,u);
		siz[u]+=siz[v];
		for(int p=min(k,siz[u]);p>=0;p--){
			for(int j=0;j<=min(p,siz[v]);j++){
				f[u][p]=max(f[u][p],f[v][j]+f[u][p-j]+w*(1ll*j*(k-j)+(siz[v]-j)*(n-k-siz[v]+j)));
			}
		}
	}
}

int main(){
	scanf("%d%d",&n,&k);k=min(k,n-k);
	for(int i=1;i<n;i++){
		int v,u,w;scanf("%d%d%d",&u,&v,&w);
		g[u].push_back(mkp(v,w));g[v].push_back(mkp(u,w));
	}
	for(int i=0;i<=n;i++)for(int j=0;j<=k;j++)f[i][j]=-INF;
	dfs(1,0);
	printf("%lld\n",f[1][k]);
	
	return 0;
}

P4516 [JSOI2018] 潜入行动

树上背包

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int K=105;
const int mod=1e9+7;
using ll = long long;
int n,k,f[N][K][2][2],siz[N],tmp[K][2][2];
//设f[u][i][a][b] 表示在 u 的子树中选择了 i 个点,a表示u是否被选择,b表示u是否已经被某个被选择的儿子监听
vector<int> g[N];

void dfs(int u,int fa){
	f[u][0][0][0]=1;f[u][1][1][0]=1;
	siz[u]=1;
	for(auto v:g[u]){
		if(v==fa)continue;
		dfs(v,u);
		memset(tmp,0,sizeof tmp);
		for(int i=0;i<=min(siz[u],k);i++){
			for(int a=0;a<2;a++){
				for(int b=0;b<2;b++){
					if(!f[u][i][a][b])continue;
					for(int j=0;j<=min(siz[v],k-i);j++){//注意此处数组的越界问题
						for(int c=0;c<2;c++){
							for(int d=0;d<2;d++){
								if(d|a){
									tmp[i+j][a][c|b]=(tmp[i+j][a][c|b]+1ll*f[u][i][a][b]*f[v][j][c][d])%mod;
								}
							}
						}
					}
				}
			}
		}
		siz[u]+=siz[v];
		for(int i=0;i<=min(k,siz[u]);i++){
			for(int a=0;a<2;a++){
				for(int b=0;b<2;b++){
					f[u][i][a][b]=(tmp[i][a][b])%mod;
				}
			}
		}
	}
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++){
		int u,v;scanf("%d%d",&u,&v);
		g[u].push_back(v);g[v].push_back(u);
	}
	dfs(1,0);
	printf("%d\n",(1ll*f[1][k][0][1]+f[1][k][1][1])%mod);
	return 0;
}

P6374 「StOI-1」树上询问

题目大意: 给定一颗有 $n $ $ (1\le n \le 5\times 10^5)$ 个节点的树,给出 \(q\) \((1 \le q \le 2\times 10^5)\) 次询问,每次询问给定一个三元组 \(\mathcal{(a,b,c)}\),求有多少个 \(i\) 满足在以 \(i\) 为树的根时 \(\mathcal{lca(a,b)=c}\) .

解题思路:
先选 \(1\) 为根,预处理倍增LCA、深度、DFS序和每个点的子树大小
对固定询问 \((a,b,c)\) ,若 \(c\) 不在 \(a\)\(b\) 的简单路径上,那么无论怎样换根,\(a,b\)\(LCA\) 都不可能是 \(c\) ,答案为 \(0\)。这个条件可用距离判断:

  • \(dist(a, c) + dist(c,b) = dist(a,b)\).

\(c\) 在路径上,考虑删除点 \(c\) 后形成的若干连通块。若根 \(r\) 落在通向 \(a\) 的那个连通块中,则从 \(r\) 看过去,\(c\) 会在 \(a\) 的下方,不可能成为 \(a,b\)\(LCA\);通向 \(b\) 的连通块同理。除此以外的所有位置,包括 \(c\) 自身,都能使 \(c\) 成为 \(LCA\)
因此答案为

  • \(n−comp(c,a)−comp(c,b)\),

其中 \(comp(c,x)\) 表示删除 \(c\) 后,通向 \(x\) 的那个连通块大小;若 \(x=c\),该值为 \(0\)
如何在以 \(1\) 为根的树上求 \(comp(c,x)\)

  • \(x=c\),大小为 \(0\)
  • \(x\) 不在 \(c\) 的子树内,通向 \(x\) 的连通块是 \(c\) 的父亲方向,大小为 \(n−siz_c\)
  • \(x\)\(c\) 的子树内,找到 \(c\)\(x\) 路径上紧邻 \(c\) 的儿子 \(y\),大小为 \(siz_y\)

寻找紧邻儿子可从 \(x\) 向上倍增到深度为\(dep_c+1\) 的点。

所以我们总时间复杂度即 \(\mathcal{O((n+q)\log{n}})\) 空间复杂度即 \(\mathcal{O(n\log{n})}\)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,q,dep[N],p[N][32],t,dfn[N],ddd,siz[N];
vector<int> g[N];

void dfs(int u,int fa){
	dfn[u]=++ddd;siz[u]=1;
	p[u][0]=fa;dep[u]=dep[fa]+1;
	for(int i=1;(1<<i)<=dep[u];i++)
		p[u][i]=p[p[u][i-1]][i-1];
	for(auto v:g[u]){
		if(v==fa)continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}

int lca(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	for(int i=t;i>=0;i--)
		if(dep[u]+(1<<i)<=dep[v])v=p[v][i];
	if(u==v)return u;
	for(int i=t;i>=0;i--)
		if(p[u][i]!=p[v][i])
			u=p[u][i],v=p[v][i];
	return p[u][0];
}
int dist(int u,int v){
	int l=lca(u,v);
	return dep[u]+dep[v]-(dep[l]<<1);
}

int find(int x,int c){
	if(x==c)return 0;
	if(dfn[x]<dfn[c]||dfn[x]>=dfn[c]+siz[c])return n-siz[c];
	for(int i=t;i>=0;i--)
		if(dep[p[x][i]]>dep[c])x=p[x][i];
	return siz[x];
}

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>n>>q;t=log2(n);
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		g[u].push_back(v);g[v].push_back(u);
	}
	dfs(1,0);
	while(q--){
		int a,b,c;cin>>a>>b>>c;
		if(dist(a,b)!=(dist(a,c)+dist(b,c))){cout<<"0\n";continue;}
		cout<<n-find(a,c)-find(b,c)<<'\n';
	}
	
	return 0;
}

[USACO05JAN] Naptime G

环形 \(dp\) 处理,注意 \(N\)\(1\) 之间的必选/必不选关系

#include<bits/stdc++.h>
using namespace std;
const int N=3835;
#define ll long long
int n,b,u[N];
ll f[2][N][2],ans;

int main(){
	cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
	cin>>n>>b;
	for(int i=1;i<=n;i++){
		cin>>u[i];
	}
	f[0][0][1]=f[0][0][0]=0;
	for(int i=2;i<=n;i++){
		int p=i&1,q=p^1;
		for(int j=1;j<=min(i,b);j++){
			f[p][j][0]=max(f[q][j][0],f[q][j][1]);
			if(j-1>0)f[p][j][1]=max(f[q][j-1][0],f[q][j-1][1]+u[i]);
			else if(j>0) f[p][j][1]=f[q][j-1][0];
			else f[p][j][1]=0;
		}
	}
	ans=max(f[n&1][b][0],f[n&1][b][1]);
	memset(f,0,sizeof f);
	f[1][1][1]=u[1];f[1][0][0]=0;
	for(int i=2;i<=n;i++){
		bool p=i&1,q=p^1;
		for(int j=0;j<=min(i,b);j++){
			f[p][j][0]=max(f[q][j][0],f[q][j][1]);
			if(j-1>0)f[p][j][1]=max(f[q][j-1][0],f[q][j-1][1]+u[i]);
			else if(j>0)f[p][j][1]=f[q][j-1][0];
			else f[p][j][1]=0;
		}
	}
	ans=max(ans,f[n&1][b][1]);
	cout<<ans<<"\n";
	
	return 0;
}

P3959 [NOIP 2017 提高组] 宝藏

把整个图分为深度为 \(1\to i\) 的各个部分分别考虑,由于总点数 \(n\le 12\) 所以可以考虑状态压缩,注意本题输入有重边

#include<bits/stdc++.h>
using namespace std;
const int N=15;
const int INF=0x3f3f3f3f;
#define ll long long
ll f[N][(1<<13)],ans=INF;
int cost[(1<<13)][(1<<13)];
int mp[N][N],n,m,tot;

void dp(int s){
	memset(f,0x3f,sizeof f);
	f[0][1<<(s-1)]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=tot;j++){
			for(int k=(j-1)&j;k;k=(k-1)&j){
				f[i][j]=min(f[i][j],f[i-1][k]+1ll*i*cost[k][j]);
			}
		}
	}
	for(int i=1;i<=n;i++)ans=min(ans,f[i][tot]);
}
int main(){
	memset(mp,0x3f,sizeof mp);
	cin>>n>>m;
	tot=(1<<n)-1;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		mp[u][v]=min(mp[u][v],w);
		mp[v][u]=mp[u][v];
	}
	for(int i=1;i<=tot;i++){
		for(int j=(i-1)&i;j;j=(j-1)&i){
			for(int t=1;t<=n;t++){
				ll tem=INF;
				if(!((i^j)&(1<<(t-1))))continue;
				for(int s=1;s<=n;s++)
					if(j&(1<<(s-1)))tem=min(tem,1ll*mp[s][t]);
				if(tem>=INF){cost[j][i]=INF;break;}
				else cost[j][i]+=tem;
			}
		}
	}
	for(int i=1;i<=n;i++){
		dp(i);
	}
	cout<<(ans==INF? 0:ans)<<'\n';
	return 0;
}

P9197 [JOI Open 2016] 摩天大楼 / Skyscraper

插入 \(dp\) 说是,集训时给的题解:
将所有数从大到小排序,依次把当前高度加入最终排列中。考虑已经加入的较大数在最终排列中的相对位置,它们会形成若干个连续段。若当前有 \(j\) 个连续段,并且最终排列的左端点、右端点中已经有 \(p,q\) 个被这些段占用,则从当前高度下降到下一个高度时,会穿过高度差的边数为 \(2j −p−q\).因此加入下一个数前,总代价要增加 \((2j −p−q)(a_{i-1} −a_i)\).

设 $ dp_{j,k,p,q} $ 表示当前已经处理若干个较大的数,形成 \(j\) 个连续段,当前累计代价为 \(k\),左右端点占用状态分别为 \(p,q\) 的方案数。

加入一个新数时,它在最终排列中可能有三类作用:

  • 新开一个连续段,段数变为 \(j+1\)
  • 接到某个已有连续段的一端,段数不变;
  • 放在两个相邻连续段之间,将它们合并,段数变为 \(j−1\)

若操作发生在最终排列左右两端,还需要相应更新端点是否已经被占用。按照可选择的位置数累加转移即可。初始时只有最大值一个数,它左右两侧是否已经成为最终端点都可以作为状态记录。最后统计 \(j=1\) 且左右端点都已占用、总代价不超过 \(L\) 的方案数
我自己结合题解与对AI提了9178个问题后写的代码:(注释起增强理解用) 时间复杂度是 \(\mathcal{O} (n^2L)\)

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=105;
const int maxl=1005;
int n,L,a[N];
using ll = long long;
ll f[2][N][maxl][2][2],ans;
// f[i][j][k][0/1][0/1] 表示前i个分j段总和为k左边界不取/取右边界不取/取的方案数

int main(){
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1,greater<int>());
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			f[1][1][0][i][j]=1;
	for(int i=2;i<=n;i++){
		int p=i&1,q=p^1;
		memset(f[p],0,sizeof f[p]);
		for(int j=1;j<i;j++){//j段
			for(int k=0;k<=L;k++){//总和为k
				for(int c=0;c<2;c++){// 左边界
					for(int d=0;d<2;d++){// 右边界
						int _k=k+((j<<1)-c-d)*(a[i-1]-a[i]);// 加上添加后的纵坐标差,即当前i的k
						if(!f[q][j][k][c][d]||_k>L)continue;//没必要继续加以及不合法的情况
						if(j>1)f[p][j+1][_k][c][d]=(f[p][j+1][_k][c][d]+(j-1)*f[q][j][k][c][d])%mod;
						if(!c){
							f[p][j+1][_k][0][d]=(f[p][j+1][_k][0][d]+f[q][j][k][c][d])%mod;
							f[p][j+1][_k][1][d]=(f[p][j+1][_k][1][d]+f[q][j][k][c][d])%mod;
						}
						if(!d){
							f[p][j+1][_k][c][0]=(f[p][j+1][_k][c][0]+f[q][j][k][c][d])%mod;
							f[p][j+1][_k][c][1]=(f[p][j+1][_k][c][1]+f[q][j][k][c][d])%mod;
						}// 上面是加成新的一段,即与当前的都不相交的情况
						if(j>1)f[p][j][_k][c][d]=(f[p][j][_k][c][d]+f[q][j][k][c][d]*(j-1)*2)%mod;
						if(!c){
							f[p][j][_k][0][d]=(f[p][j][_k][0][d]+f[q][j][k][c][d])%mod;
							f[p][j][_k][1][d]=(f[p][j][_k][1][d]+f[q][j][k][c][d])%mod;
						}
						if(!d){
							f[p][j][_k][c][0]=(f[p][j][_k][c][0]+f[q][j][k][c][d])%mod;
							f[p][j][_k][c][1]=(f[p][j][_k][c][1]+f[q][j][k][c][d])%mod;
						}// 上面是与其中的一段相交
						if(j>1)f[p][j-1][_k][c][d]=(f[p][j-1][_k][c][d]+f[q][j][k][c][d]*(j-1))%mod;
						//上面是插进相邻的空隙中
					}
				}
			}
		}
	}
	for(int i=0;i<=L;i++)
		ans=(ans+f[n&1][1][i][1][1])%mod;
	printf("%lld\n",ans);
	return 0;
}

P2704 [NOI2001] 炮兵阵地

状压 \(dp\) 板中板

#include<bits/stdc++.h>
using namespace std;
const int N=105;
const int M=10;
int f[2][(1<<(M+1))][(1<<(M+1))],n,m,cnt,masks[(1<<(M+1))],ans,e[N];
char mp[N][M+5];


int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",mp[i]+1);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(mp[i][j]=='H')e[i]|=(1<<(j-1));
		}
	}
	int tot=(1<<m)-1;
	for(int j=0;j<=tot;j++){
		if((j&(j<<1))==0&&(j&(j<<2))==0){
			masks[++cnt]=j;
		}
	}
	memset(f,0xc0,sizeof f);
	f[0][0][0]=0;
	for(int i=1;i<=n;i++){
		int p=i&1,q=p^1;
		memset(f[p],0xc0,sizeof f[p]);
		for(int j=1;j<=cnt;j++){
			int r=masks[j];
			if(r&e[i])continue;
			for(int k=1;k<=cnt;k++){
				int t=masks[k];
				if(t&e[i-1]||r&t)continue;
				for(int l=1;l<=cnt;l++){
					int y=masks[l];
					if(i>1&&y&e[i-2])continue;
					if((t&y)>0||(r&y)>0)continue;
					f[p][r][t]=max(f[p][r][t],f[q][t][y]+__builtin_popcount(r));
					ans=max(ans,f[p][r][t]);
				}
			}
		}
	}
	printf("%d\n",ans);
	
	return 0;
}
posted @ 2026-07-14 19:49  dgcsjfs  阅读(12)  评论(4)    收藏  举报