【杂题汇总】NOIP 2022 杂题选做

自己没事卷的题,也有许多其他dalao博客里的杂题

Examination

四个优先队列,人类智慧贪心,详情见这里

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<algorithm>
#define pii pair<int,int>

using namespace std;

const int maxn=3e5+5;

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

int n,x,y,ans;
priority_queue<pii,vector<pii>,less<pii> > q;
priority_queue<int,vector<int>,less<int> > A,B; 
priority_queue<int,vector<int>,greater<int> > not_B;

int main()
{
	n=read();

	for(int i=1;i<=n;i++)
	{
		x=read(),y=read();
		if(x<y) A.push(x),B.push(y);
		else q.push(make_pair(x,y)),ans++;
	}
	
	while(!B.empty())
	{
		int k=B.top();B.pop();
		
		if(!A.empty() && A.top()>=k)
		{
			A.pop();continue;
		}
		
		while(!q.empty() && q.top().first>=k)
		{
			not_B.push(q.top().second);q.pop();
		}
		
		if(not_B.empty()) cout<<-1,exit(0);
		
		ans--,B.push(not_B.top()),not_B.pop();
	}
	
	cout<<ans;
	
	return 0;
}

Traffic Jams in the Land

人类智慧线段树,随机跳题跳到这等好题 \(2\leq a_i\leq 6\) ,\(LCM(2,3,4,5,6)\)为60,因此当前时间超过60以后让其对60取模不会影响当前时间是否能被 \(a_i\) 整除,这样每次更新时我们可以只对当前时间为0~59的情况进行处理即可

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=1e5+5;

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

int n,m;
int a[maxn];

struct s_t
{
	int l,r;
	int val[65];
}t[maxn*4];

void push_up(int p)
{
	for(int i=0;i<60;i++)
	{
		t[p].val[i]=t[p*2+1].val[(i+t[p*2].val[i])%60]+t[p*2].val[i];
	}
}

void build(int p,int l,int r)
{
	t[p].l=l,t[p].r=r;
	if(t[p].l==t[p].r)
	{
		for(int i=0;i<60;i++)
		{
			t[p].val[i]=1+(i%a[l]==0);
		}
		return ;
	}
	int mid=(l+r)>>1;
	build(p*2,l,mid),build(p*2+1,mid+1,r);
	push_up(p);
}

void update(int p,int pos)
{
	if(t[p].l==t[p].r)
	{
		for(int i=0;i<60;i++)
		{
			t[p].val[i]=1+(i%a[pos]==0);
		}
		return ;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(pos<=mid) update(p*2,pos);
	else update(p*2+1,pos);
	push_up(p);
}

int query(int p,int l,int r,int now)
{
	if(l<=t[p].l && t[p].r<=r)
	{
		return t[p].val[now];
	}
	int mid=(t[p].l+t[p].r)>>1;
	int ans=0;
	if(l<=mid) ans+=query(p*2,l,r,now);
	if(r>mid) ans+=query(p*2+1,l,r,(now+ans)%60);
	return ans;
}

int main()
{
	n=read(); for(int i=1;i<=n;i++) a[i]=read();
	
	build(1,1,n),m=read();
	
	while(m--)
	{
		char opt[10];
		scanf("%s",opt);
		if(opt[0]=='C')
		{
			int x=read(),d=read();
			a[x]=d;update(1,x);
		}
		else
		{
			int l=read(),r=read();
			printf("%d\n",query(1,l,r-1,0));
		}
	}
	
	return 0;
}

Poor Turkeys

比较水的黑题 正着比较难枚举,考虑时光倒流,分类讨论如何让一只火鸡活下来,接下来倒着枚举火鸡,如果这只火鸡无法满足活下来的条件,那它就得死,在考虑两只火鸡如何同时活下来,如果不满足则那两只火鸡必死一只,最后统计出ans

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=1e5+10;

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

int n,m,ans;
int x[maxn];
int y[maxn];
bool live[410];
bool s[410][410];

int main()
{
	n=read(),m=read();
	
	for(int i=1;i<=m;i++)
	{
		x[i]=read();
		y[i]=read();
	}
	
	for(int i=1;i<=n;i++)
	{
		s[i][i]=true;
		for(int j=m;j>=1;j--)
		{
			bool a=s[i][x[j]];
			bool b=s[i][y[j]];
			if(a && b) {live[i]=true;break;}
			else if(a) s[i][y[j]]=true;
			else if(b) s[i][x[j]]=true;
		}
	}
	
	for(int i=1;i<n;i++)
	{
		if(live[i]) continue;
		for(int j=i+1;j<=n;j++)
		{
			if(live[j]) continue;
			bool cnt=true;
			for(int k=1;k<=n;k++)
			{
				if(s[i][k] && s[j][k]) cnt=false;
			}
			ans+=cnt;
		}
	}
	
	cout<<ans;
	
	return 0;
}

Valera and Queries

说实话我真没看出来这是数据结构题 正难则反,对于每个询问,我们可以用总线段数量减去不合法的线段数量,下面说一下判断合法/不合法

我们可以将每一个点转化为区间,区间为上一个点+1到这一个点-1,将所有这样的区间搞出来,判断有没有线段被完全包含于某一一个区间,如果被完全包含的话说明不合法,用离线树状数组统计出来

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=1e6+5;

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

int n,m,cnt;
int t[maxn];
int num[maxn];
int ans[maxn];
struct que
{
	int l,r,id;
}q[maxn];

int lowbit(int x){ return x&-x; }

void update(int x)
{
	for(;x<maxn;x+=lowbit(x)) t[x]++;
}

int query(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=t[x];
	return res;
}

bool cmp(que x,que y)
{
	if(x.l!=y.l) return x.l>y.l;
	if(x.r!=y.r) return x.r<y.r;
	return x.id<y.id;
}

int main()
{
	n=read(),m=read();
	
	for(int i=1;i<=n;i++)
	{
		q[++cnt].l=read();
		q[cnt].r=read();
	}
	
	for(int i=1;i<=m;i++)
	{
		int tot=read();
		for(int j=1;j<=tot;j++)
		{
			num[j]=read();
		}
		for(int j=1;j<=tot;j++)
		{
			if(num[j]>1 && num[j-1]<num[j]-1)
			{
				q[++cnt].l=num[j-1]+1;
				q[cnt].r=num[j]-1;
				q[cnt].id=i;
			}
		}
		q[++cnt].l=num[tot]+1;
		q[cnt].r=maxn-1,q[cnt].id=i;
	}
	
	sort(q+1,q+cnt+1,cmp);
	
	for(int i=1;i<=cnt;i++)
	{
		if(q[i].id) ans[q[i].id]+=query(q[i].r);
		else update(q[i].r);
	}
	
	for(int i=1;i<=m;i++)
	{
		cout<<n-ans[i]<<endl;
	}
	
	return 0;
}

Anton and Tree

来自@Clover_BY dalao的讲解(但他没有博客园)

现将每个颜色相同的连通块缩成一个点,然后手模样例可以发现每次都点一个点,使得最大连通块向叶节点的范围扩展了一圈,因此答案就是最大深度-1

使最大深度最小,则我们每次都应该点的点为直径中点,答案即为直径长度的一半

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=200010;

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

int cnt,n,tot,root;
int u[maxn],sign[maxn];
int bel[maxn],deep[maxn];
int val[maxn],head[maxn];
int max_deep[maxn],v[maxn];
struct edge
{
	int to,next;
}e[maxn*2];

void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].next=head[x];
	head[x]=tot;
}

void resign(int x,int id)
{
	bel[x]=id;
	for(int i=head[x];i;i=e[i].next)
	{
		int to=e[i].to;
		if(bel[to] || val[x]!=val[to]) continue;
		resign(to,id);
	}
}

void dfs_first(int x,int fa)
{
	deep[x]=deep[fa]+1;
	max_deep[x]=deep[x],sign[x]=x;
	for(int i=head[x];i;i=e[i].next)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs_first(to,x);
		if(max_deep[to]>max_deep[x])
		{
			max_deep[x]=max_deep[to];
			sign[x]=sign[to];
		}
	}
}

void dfs_second(int x,int fa)
{
	deep[x]=deep[fa]+1;
	max_deep[x]=deep[x];
	for(int i=head[x];i;i=e[i].next)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs_second(to,x);
		if(max_deep[to]>max_deep[x])
		{
			max_deep[x]=max_deep[to];
		}
	}
}

int main()
{
	n=read();
	
	for(int i=1;i<=n;i++)
	{
		val[i]=read();
	}
	
	for(int i=1;i<n;i++)
	{
		u[i]=read(),v[i]=read();
		add(u[i],v[i]),add(v[i],u[i]);
	}
	
	for(int i=1;i<=n;i++)
	{
		if(!bel[i]) resign(i,++cnt);
	}
	
	memset(head,0,sizeof(head)),tot=0;
	
	for(int i=1;i<n;i++)
	{
		if(bel[u[i]]!=bel[v[i]])
		{
			add(bel[u[i]],bel[v[i]]);
			add(bel[v[i]],bel[u[i]]);
		}
	}
	
	dfs_first(1,0);root=sign[1];
	deep[root]=1;dfs_second(root,0);
	cout<<(max_deep[root]>>1);
	
	return 0;
}

Tufurama

我们将其转化一下(先调一下)变成了 \(j<i\)\(j\leq a_i\)\(i\leq a_j\)

变一下型,可以确定 \(j\) 的范围 \([1,min(i-1,a_i)]\),则可以在这个范围内找到 \(a_j>i\) 的数量

用主席树即可(不知道为啥这么慢,比rk1树状数组慢了一倍)

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=2e5+5;

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

long long ans;
int n,a[maxn];
int root[maxn],cnt;
struct s_t
{
	int ls,rs;
	int sum;
}t[maxn<<5];

void update(int &p,int las,int l,int r,int pos)
{
	p=++cnt;t[p]=t[las];
	if(l==r) {t[p].sum++;return ;}
	int mid=(l+r)>>1;
	if(pos<=mid) update(t[p].ls,t[las].ls,l,mid,pos);
	else update(t[p].rs,t[las].rs,mid+1,r,pos);
	t[p].sum=t[t[p].ls].sum+t[t[p].rs].sum;
}

int query(int p,int las,int l,int r,int L,int R)
{
	if(L<=l && r<=R) return t[p].sum;
	int mid=(l+r)>>1,ans=0;
	if(L<=mid) ans+=query(t[p].ls,t[las].ls,l,mid,L,R);
	if(R>mid) ans+=query(t[p].rs,t[las].rs,mid+1,r,L,R);
	return ans;
}

int main()
{
	n=read();
	
	for(int i=1;i<=n;i++)
	{
		a[i]=min(read(),n);
	}
	
	for(int i=1;i<=n;i++)
	{
		update(root[i],root[i-1],1,n,a[i]);
	}
	
	for(int i=1;i<=n;i++)
	{
		ans+=query(root[min(i-1,a[i])],root[i],1,n,i,n);
	}
	
	printf("%lld",ans);
	
	return 0;
}

Sonya and Informatics

设状态 \(dp_{i,j}\) 为第i次操作前面 \(m\) 个数有 \(j\) 个零(\(m\) 为零的个数)分类讨论即可

操作次数太多,矩阵加速递推

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=110;
const int mod=1e9+7;

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

int n,k,m,a[maxn],t;
int A[maxn],B[maxn],C[maxn];

struct Matrix
{
	int MA[maxn][maxn];
	void init()
	{
		memset(MA,0,sizeof(MA));
	}
	void build()
	{
		for(int i=0;i<=n;i++) MA[i][i]=1;
	}
}ans,M;

Matrix operator*(const Matrix &x,const Matrix &y)
{
	Matrix z;z.init();
	for(int i=0;i<=m;i++)
		for(int k=0;k<=m;k++)
			for(int j=0;j<=m;j++)
				z.MA[i][j]=(z.MA[i][j]+(x.MA[i][k]*y.MA[k][j]%mod))%mod;
	return z;
}

void ksm(Matrix a,int b)
{
	ans.init(),ans.build();
	for(;b;b>>=1,a=a*a)
	{
		if(b&1) ans=ans*a;
	}
}

int KSM(int a,int b)
{
	int ans=1;
	for(;b;b>>=1,a=(a*a)%mod)
	{
		if(b&1) ans=(ans*a)%mod; 
	}
	return ans;
}

signed main()
{
	n=read(),k=read();
	
	for(int i=1;i<=n;i++) a[i]=read();
	
	for(int i=1;i<=n;i++) m+=(a[i]==0);
	for(int i=1;i<=m;i++) t+=(a[i]==0);
	
	for(int i=0;i<=m;i++)
	{
		A[i]=((m-i)*(m-i))%mod;
		B[i]=(i*(n-2*m+i))%mod;
		C[i]=(n*(n-1)/2-A[i]-B[i])%mod;
	}
	
	M.MA[0][0]=C[0];
	M.MA[0][1]=B[1];
	for(int i=1;i<m;i++)
	{
		M.MA[i][i]=C[i];
		M.MA[i][i-1]=A[i-1];
		M.MA[i][i+1]=B[i+1];
	}
	M.MA[m][m-1]=A[m-1];
	M.MA[m][m]=C[m];
	
	ksm(M,k);

	int cnt=0;
	for(int i=0;i<=m;i++)
	{
		cnt=(cnt+ans.MA[i][t])%mod;
	}
	
	cout<<ans.MA[m][t]*KSM(cnt,mod-2)%mod;
	
	return 0;
}

[APIO 2015] 八邻旁之桥

分类讨论,显然,同岸的情况可以不用特殊计算

着重讨论隔岸的情况,注意到 \(k\leq 2\)

\(k=1\) 的情况,设桥的位置为pos,可以知道答案为 \(\sum_{i=1}^{n}|x_i-pos|+|y_i-pos|\),那么 \(x_i\)\(y_i\) 可以看做一样的

我们就可以把他们放进一个数组里,显然pos为他们的中位数,可以得出答案

\(k=2\) 的情况,枚举分割点,两侧就变成了 \(k=1\) 的情况,通过权值线段树快速的找出中位数然后求解

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<climits>
#define int long long

using namespace std;

const int maxn=200010;

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

int n,k;

namespace first
{
	int num[maxn],cnt=0;
	int ans,m=0,x,y;
	void main()
	{
		for(int i=1;i<=n;i++)
		{
			char a[5],b[5];
			scanf("%s",a),x=read();
			scanf("%s",b),y=read();
			if(a[0]==b[0])
				{ans+=abs(x-y);continue;}
			m++,num[++cnt]=x,num[++cnt]=y;
		}
		sort(num+1,num+cnt+1);
		int mid=(num[cnt>>1]);
		for(int i=1;i<=cnt;i++)
			ans+=abs(num[i]-mid);
		printf("%lld",ans+m);
	}
}

namespace second
{
	int ans,cnt,tot;
	int sum[maxn];
	
	struct s_t
	{
		int l,r;
		int val;
		int cnt;
	}t[maxn*4];
	
	struct seg
	{
		int s,t;
	}s[maxn];
	
	int num[maxn];
	
	bool cmp(seg a,seg b)
	{
		return a.s+a.t<b.s+b.t;
	}
	
	void build(int p,int l,int r)
	{
		t[p].l=l,t[p].r=r;
		t[p].val=t[p].cnt=0;
		if(l==r) return ;
		int mid=(l+r)>>1;
		build(p*2,l,mid);
		build(p*2+1,mid+1,r);
	}
	
	void update(int p,int k,int val)
	{
		if(t[p].l==t[p].r)
		{
			t[p].cnt++;
			t[p].val+=val;
			return ;
		}
		int mid=(t[p].l+t[p].r)>>1;
		if(k<=mid) update(p*2,k,val);
		else update(p*2+1,k,val);
		t[p].cnt=t[p*2].cnt+t[p*2+1].cnt;
		t[p].val=t[p*2].val+t[p*2+1].val;
	}
	
	int query(int p,int k)
	{
		if(t[p].l==t[p].r) return num[t[p].l]*k;
		int mid=(t[p].l+t[p].r)>>1;
		if(k<=t[p*2].cnt) return query(p*2,k);
		else return t[p*2].val+query(p*2+1,k-t[p*2].cnt);
	}
	
	void work()
	{
		build(1,1,tot);
		
		for(int i=1;i<=cnt;i++)
		{
			s[i].s=lower_bound(num+1,num+tot+1,s[i].s)-num;
			s[i].t=lower_bound(num+1,num+tot+1,s[i].t)-num;
			update(1,s[i].s,num[s[i].s]);
			update(1,s[i].t,num[s[i].t]);
			sum[i]=abs(t[1].val-2*query(1,i));
		}
		
		int mid=LLONG_MAX;
		
		build(1,1,tot);
		
		for(int i=cnt;i>=1;i--)
		{
			update(1,s[i].s,num[s[i].s]);
			update(1,s[i].t,num[s[i].t]);
			mid=min(mid,sum[i-1]+abs(t[1].val-2*query(1,cnt-i+1)));
		}
		
		printf("%lld",ans+mid);
	}
	
	void main()
	{
		for(int i=1;i<=n;i++)
		{
			char a[5],b[5];int x,y;
			scanf("%s",a),x=read();
			scanf("%s",b),y=read();
			if(a[0]==b[0])
				{ans+=abs(x-y);continue;}
			if(x>y) swap(x,y);ans++;
			s[++cnt].s=x,s[cnt].t=y;
			num[++tot]=x,num[++tot]=y;
		}
		if(cnt==0) {printf("%lld",ans);return;}
		sort(num+1,num+tot+1);
		sort(s+1,s+cnt+1,cmp);
		tot=unique(num+1,num+tot+1)-num-1;
		work();
	}
}

signed main()
{
	k=read(),n=read();
	
	if(k==1) first::main();
	else second::main();
	
	return 0;
}

[APIO 2015] 雅加达的摩天楼

根号分治思想+最短路,对于 \(p\leq \sqrt\frac{n}{3}\) 的情况直接建边,否则对每个doge建一层分层图来搞

还卡了Dij放SPFA过

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<algorithm>
#define pii pair<int,int>

using namespace std;

const int maxx=105;
const int maxn=30010;
const int maxm=18000010;
const int max_num=3100005;

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

int n,m,tot,siz;
int head[max_num];
bool vis[max_num];
struct edge
{
	int to,next,val;
}e[maxm];
int dis[max_num],s,t;
int flor[maxx][maxn];

void add(int x,int y,int val)
{
	e[++tot].to=y;
	e[tot].next=head[x];
	e[tot].val=val;
	head[x]=tot;
}

int SPFA(int x,int y)
{
	queue <int> q;
	memset(dis,0x3f3f3f,sizeof(dis));
	dis[x]=0,vis[x]=true,q.push(x);
	while(!q.empty())
	{
		int k=q.front();q.pop();
		vis[k]=false;
		for(int i=head[k];i;i=e[i].next)
		{
			int to=e[i].to;
			if(dis[to]>dis[k]+e[i].val)
			{
				dis[to]=dis[k]+e[i].val;
				if(vis[to]==false)
					vis[to]=true,q.push(to);
			}
		}
	}
	return dis[y]==1061109567 ? -1 : dis[y];
}

int main()
{
	n=read(),m=read();
	
	siz=sqrt(n/3);
	
	for(int i=1;i<=siz;i++)
		for(int j=0;j<=n-1;j++)	flor[i][j]=i*n+j;
	
	for(int i=1;i<=siz;i++)
	{
		for(int j=0;j<=n-1;j++)
		{
			add(flor[i][j],j,0);
			if(j+i>=n) break;
			add(flor[i][j],flor[i][i+j],1);
			add(flor[i][i+j],flor[i][j],1);
			add(flor[i][i+j],i+j,0);
		}
	}
	
	for(int i=0;i<m;i++)
	{
		int b=read(),p=read();
		if(p<=siz)
			add(b,flor[p][b],0);
		else
		{
			for(int j=1;b+p*j<n;j++) add(b,b+p*j,j);
			for(int j=1;b-p*j>=0;j++) add(b,b-p*j,j);
		}
		if(i==0) s=b;
		if(i==1) t=b;
	}
	
	printf("%d",SPFA(s,t));
	
	return 0;
}

[APIO 2015] 巴厘岛的雕塑

数位DP,还是踏马的分类讨论,\(A=1\) 设状态 \(f_i\) 表示当前位放0最少分几段,从高位到低位枚举,最后与 \(B\) 进行比较

\(A\neq 1\),bool状态 \(dp_{i,j}\) 表示前 \(i\) 个数分成 \(j\) 段这一位能不能为0,然后与上述情况进行类似的转移

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=2010;

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

int ans,res,f[maxn];
bool dp[maxn][maxn];
int s[maxn],n,a,b;

namespace solve
{
	void main()
	{
		for(int w=45;w>=0;w--)
		{
			memset(dp,false,sizeof(dp));
			dp[0][0]=1,res=(ans|((1ll<<w)-1ll));
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=min(b,i);j++)
				{
					for(int k=j-1;k<=i-1;k++)
					{
						if(!dp[k][j-1]) continue;
						if((res|(s[i]-s[k]))!=res) continue;
						dp[i][j]=true; break;
					}
				}
			}
			bool sign=false;
			for(int i=a;i<=b;i++)
				if(dp[n][i]==true) sign=true;
			if(!sign) ans|=(1ll<<w);
		}
	
		printf("%lld",ans);
	}
}

namespace work
{
	void main()
	{
		for(int w=45;w>=0;w--)
		{
			memset(f,0x3f3f3f,sizeof(f));
			f[0]=0,res=(ans|((1ll<<w)-1ll));
			for(int i=1;i<=n;i++)
			{
				for(int k=0;k<i;k++)
				{
					if((res|(s[i]-s[k]))!=res) continue;
					f[i]=min(f[i],f[k]+1);
				}
			}			
			if(f[n]>b) ans|=(1ll<<w);
		}
	
		printf("%lld",ans);
	}
}

signed main()
{
	n=read(),a=read(),b=read();
	
	for(int i=1;i<=n;i++)
		s[i]=s[i-1]+read();
	
	if(a==1) work::main();
	else solve::main();
	
	return 0;
}

[NOI 2015] 寿司晚宴

状压神题,将小质因子(前八个质因子)压成状态,剩下的大质因子另存起来,然后开三个相同的 \(dp\) 数组随便维护

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=510;

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

int prime[10]={0,2,3,5,7,11,13,17,19},ans,n,p;
int dp[maxn][maxn],f1[maxn][maxn],f2[maxn][maxn];
struct num
{
	int val,S=0,big=-1;
	void init()
	{
		for(int i=1;i<=8;i++)
		{
			if(val%prime[i]) continue;
			S|=(1<<i-1);
			while(val%prime[i]==0) val/=prime[i];
		}
		if(val!=1) big=val;
	}
}a[maxn];

bool cmp(num a,num b)
{
	return a.big<b.big;
}

signed main()
{
	n=read(),p=read();
	
	for(int i=2;i<=n;i++) 
		a[i-1].val=i,a[i-1].init();
	
	sort(a+1,a+n,cmp);
		
	dp[0][0]=1;
	
	for(int i=1;i<n;i++)
	{
		if(i==1 || a[i].big!=a[i-1].big || a[i].big==-1)
			memcpy(f1,dp,sizeof(f1)),memcpy(f2,dp,sizeof(f2));
		for(int j=255;j>=0;j--)
		{
			for(int k=255;k>=0;k--)
			{
				if(k&j) continue;
				if((k&a[i].S)==0) f1[j|a[i].S][k]=(f1[j|a[i].S][k]+f1[j][k])%p;
				if((j&a[i].S)==0) f2[j][k|a[i].S]=(f2[j][k|a[i].S]+f2[j][k])%p;
			}
		}
		if(i==n-1 || a[i].big!=a[i+1].big || a[i].big==-1)
		{
			for(int j=0;j<=255;j++)
			{
				for(int k=0;k<=255;k++)
				{
					if(k&j) continue;
					dp[j][k]=(f1[j][k]+f2[j][k]-dp[j][k]+p)%p;
				}
			}
		}
	}
	
	for(int j=0;j<=255;j++)
	{
		for(int k=0;k<=255;k++)
		{
			if(k&j) continue;
			ans=(ans+dp[j][k])%p;
		}
	}
	
	printf("%lld",ans);
	
	return 0;
}

[Ynoi 2017] 由乃的玉米田

第一道Ynoi祭!根号分治,前三种操作上个月已经做过了,主要是第四种,\(x\geq \sqrt n\) 莫队直接维护了就行,否则对于每个 \(x\) 找到对于每个 \(i\) 来说最近的满足条件的值然后判断是不是在区间内即可

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<bitset>
#include<algorithm>

using namespace std;

const int maxn=1e5+5;

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

int n,m,N;
int val[maxn];
int pre[maxn];
int cnt[maxn];
int ans[maxn];
int bel[maxn];
int near[maxn];
int siz,num,sum;
bitset <maxn> liv;
bitset <maxn> rev;

struct que
{
	int id,l,r,opt,x;
}q[maxn];

vector <que> ask[maxn];

bool cmp(que a,que b)
{
	if(bel[a.l]!=bel[b.l])
		return bel[a.l]<bel[b.l];
	else 
		return a.r<b.r;
}

void add(int x)
{
	if(cnt[val[x]]==0)
		liv[val[x]]=rev[N-val[x]]=1;
	cnt[val[x]]++;
}

void del(int x)
{
	cnt[val[x]]--;
	if(cnt[val[x]]==0)
		liv[val[x]]=rev[N-val[x]]=0;
}

int main()
{
	n=read(),m=read();
	
	for(int i=1;i<=n;i++) val[i]=read();
	
	N=maxn;	siz=sqrt(n),num=ceil((double)n/siz);
	
	for(int i=1;i<=num;i++)
		for(int j=(i-1)*siz+1;j<=min(n,i*siz);j++) bel[j]=i;
	
	for(int i=1;i<=m;i++)
	{
		int opt,l,r,x;
		opt=read(),l=read();
		r=read(),x=read();
		if(opt==4 && x<siz)
			ask[x].push_back({i,l,r,opt,x});
		else q[++sum]=que{i,l,r,opt,x};
	}
	
	sort(q+1,q+sum+1,cmp);
	
	int l=1,r=0;
	
	for(int i=1;i<=sum;i++)
	{
		while(l<q[i].l) del(l++);
		while(l>q[i].l) add(--l);
		while(r<q[i].r) add(++r);
		while(r>q[i].r) del(r--);
		int opt=q[i].opt,x=q[i].x;
		if(opt==1) ans[q[i].id]=(liv&(liv<<x)).any();
		if(opt==2) ans[q[i].id]=(liv&(rev>>(N-x))).any();
		if(opt==3)
		{
			for(int k=1;k*k<=x;k++)
			{
				if(x%k) continue;
				ans[q[i].id]=(liv[k]&&liv[x/k]);
				if(ans[q[i].id]) break;
			}
		}
		if(opt==4)
		{
			for(int k=1;k*x<=n;k++)
				if(liv[k] && liv[k*x]) {ans[q[i].id]=true;break;}
		}
	}
	
	for(int i=1;i<sqrt(n);i++)
	{
		if(ask[i].empty()) continue;
		memset(pre,0,sizeof(pre));
		memset(near,0,sizeof(near));
		int l=0;
		for(int j=1;j<=n;j++)
		{
			pre[val[j]]=j;
			if(val[j]*i<=N)	l=max(l,pre[val[j]*i]);
			if(val[j]%i==0)	l=max(l,pre[val[j]/i]);
			near[j]=l;
		}
		for(int k=0;k<ask[i].size();k++)
			ans[ask[i][k].id]=(ask[i][k].l<=near[ask[i][k].r]);
	}
	
	for(int i=1;i<=m;i++)
		ans[i] ? puts("yuno") : puts("yumi");
	
	return 0;
}

[WF 2013] Matryoshka

区间DP,对于每个小区间合并为一个完好的套娃集,设 \(dp_{i,j}\) 表示合并区间 \(i,j\) 的最小代价,最后在开一个DP数组合并所有套娃集的答案,判断合不合法以及计算贡献可以通过预处理信息来搞

有意思的

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<climits>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=510;
const int INF=1e9;

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

int max_val[maxn][maxn];
int min_val[maxn][maxn];
bool vis[maxn],can[maxn][maxn];
int n,a[maxn],maxm,dp[maxn][maxn];
int salr[maxn][maxn][maxn],ans[maxn];

int main()
{
	while(scanf("%d",&n)==1)
	{
		for(int i=1;i<=n;i++) 
			a[i]=read(),maxm=max(maxm,a[i]);
	
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				dp[i][j]=INF;can[i][j]=true;
				max_val[i][j]=0;min_val[i][j]=INF;
			}
		}
		
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				for(int k=1;k<=500;k++)	salr[i][j][k]=0;
					
		for(int i=1;i<=n;i++) dp[i][i]=0;
	
		for(int l=1;l<=n;l++)
		{
			for(int r=l;r<=n;r++)
			{
				for(int k=1;k<=500;k++) vis[k]=false;
				for(int k=l;k<=r;k++)
				{
					max_val[l][r]=max(max_val[l][r],a[k]);
					min_val[l][r]=min(min_val[l][r],a[k]);
					for(int m=a[k]+1;m<=maxm;m++)
					{
						salr[l][r][m]++;
					}
					if(vis[a[k]]) can[l][r]=false;
					vis[a[k]]=true;
				}
			}
		}

		for(int len=1;len<=n;len++)
		{
			for(int l=1;l+len-1<=n;l++)
			{
				int r=l+len-1;
				if(!can[l][r]) continue;
				for(int k=l;k+1<=r;k++)
				{
					if(min_val[l][k]<min_val[k+1][r])
					{
						dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+len-salr[l][k][min_val[k+1][r]]);
					}
					else
					{
						dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+len-salr[k+1][r][min_val[l][k]]);
					}
				}
			}
		}
	
		for(int i=1;i<=n;i++) ans[i]=INF;
		
		ans[0]=0;
		
		for(int l=1;l<=n;l++)
		{
			for(int r=l;r<=n;r++)
			{
				if(max_val[l][r]==r-l+1 && can[l][r])
				{
					ans[r]=min(ans[r],ans[l-1]+dp[l][r]);
				}
			}
		}
	
		if(ans[n]==INF) cout<<"impossible"<<endl;
		else cout<<ans[n]<<endl;
	}

	
	return 0;
}

[APIO 2014] 连珠线

换根DP(通过换根把所有蓝线都转变为父-我-子的类型),然后设 \(dp_{i,0/1}\) 表示节点 \(i\) 作为/不作为蓝线中点时的最大得分,然后按套路换根

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=200010;

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

int n,tot,ans;
int head[maxn];
int dp[maxn][2];
int max_val[maxn][2];
struct edge
{
	int to,next,val;
}e[maxn*2];

void add(int x,int y,int z)
{
	e[++tot].to=y;
	e[tot].val=z;
	e[tot].next=head[x];
	head[x]=tot;
}

void dfs1(int x,int fa)
{
	for(int i=head[x];i;i=e[i].next)
	{
		int to=e[i].to;if(to==fa){continue;}dfs1(to,x);
		dp[x][0]+=max(dp[to][0],dp[to][1]+e[i].val);
		dp[x][1]+=max(dp[to][0],dp[to][1]+e[i].val);
		int val=dp[to][0]+e[i].val-max(dp[to][0],dp[to][1]+e[i].val);
		if(val>max_val[x][0]) max_val[x][1]=max_val[x][0],max_val[x][0]=val;
		else if(val>max_val[x][1]) max_val[x][1]=val;
	}
	dp[x][1]+=max_val[x][0];
}

void dfs2(int x,int fa)
{
	int val;  ans=max(ans,dp[x][0]);
	int dp0=dp[x][0],dp1=dp[x][1];
	int NO_1=max_val[x][0],NO_2=max_val[x][1]; 
	for(int i=head[x];i;i=e[i].next)
	{
		int to=e[i].to;	if(to==fa) continue;
		dp[x][0]-=max(dp[to][0],dp[to][1]+e[i].val);
		dp[x][1]-=max(dp[to][0],dp[to][1]+e[i].val);
		val=dp[to][0]+e[i].val-max(dp[to][0],dp[to][1]+e[i].val);
		if(val==max_val[x][0]) dp[x][1]+=max_val[x][1]-val;
		dp[to][0]+=max(dp[x][0],dp[x][1]+e[i].val);
		dp[to][1]+=max(dp[x][0],dp[x][1]+e[i].val);
		dp[to][1]-=max_val[to][0];
		val=dp[x][0]+e[i].val-max(dp[x][0],dp[x][1]+e[i].val);
		if(val>max_val[to][0]) max_val[to][1]=max_val[to][0],max_val[to][0]=val;
		else if(val>max_val[to][1]) max_val[to][1]=val;
		dp[to][1]+=max_val[to][0];
		dfs2(to,x);
		dp[x][0]=dp0,dp[x][1]=dp1;max_val[x][0]=NO_1,max_val[x][1]=NO_2;
	}
}

int main()
{
	n=read();
	
	for(int i=1;i<n;i++)
	{
		int u=read();
		int v=read();
		int w=read();
		add(u,v,w),add(v,u,w);
	}
	
	memset(max_val,0xcf,sizeof(max_val));
	
	dfs1(1,0),dfs2(1,0); printf("%d",ans); 
	
	return 0;
}

Intervals

人类智慧DP,虽说是套路题,设 \(dp_{i,j}\) 表示第 \(i\) 个位置,上一个0在 \(j\) 的最大得分

\[dp_{i,i}=min_{j<i}(dp_{i-1,j}) \]

\[dp_{i,j}=dp_{i,j}+\sum_{r_x=i,l_x\leq j}a_x \]

然后在区间右端点时讨论贡献

线段树优化一下(直接把 \(dp\) 值放在线段树上)

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=2e5+5;

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

int n,m;
struct s_t
{
	int l,r;
	int tag;
	int val;
}t[maxn*4];
struct que
{
	int l,r,v;
}q[maxn];
vector <que> w[maxn];

void build(int p,int l,int r)
{
	t[p].l=l,t[p].r=r;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
}

void push_down(int p)
{
	if(t[p].tag)
	{
		t[p*2].val+=t[p].tag;
		t[p*2+1].val+=t[p].tag;
		t[p*2].tag+=t[p].tag;
		t[p*2+1].tag+=t[p].tag;
		t[p].tag=0;
	}
}

void update(int p,int l,int r,int k)
{
	if(l<=t[p].l && t[p].r<=r)
	{
		t[p].val+=k;
		t[p].tag+=k;
		return ;
	}
	push_down(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) update(p*2,l,r,k);
	if(r>mid) update(p*2+1,l,r,k);
	t[p].val=max(t[p*2].val,t[p*2+1].val);
}

signed main()
{
	n=read(),m=read();
	
	for(int i=1;i<=m;i++)
	{
		q[i].l=read();
		q[i].r=read();
		q[i].v=read();
		w[q[i].r].push_back(q[i]);
	}
	
	build(1,1,n);
	
	for(int i=1;i<=n;i++)
	{
		update(1,i,i,max(t[1].val,0ll));
		for(int j=0;j<w[i].size();j++)
		{
			update(1,w[i][j].l,w[i][j].r,w[i][j].v);
		}
	}
	
	cout<<max(t[1].val,0ll);
	
	return 0;
}

陌上开花

三维偏序板题,第一维sort掉,然后CDQ内部将区间 \(l,mid\)\(mid+1,r\) 分别按第二维sort,树状数组搞定第三维

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn=2e5+5;

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

int n,cnt,m,k;
int ans[maxn];
struct num
{
	int a,b,c,sum,ans;
}val[maxn],cdq[maxn];
int t[maxn];

bool cmp1(num x,num y)
{
	if(x.a==y.a)
		if(x.b==y.b) return x.c<y.c;
		else return x.b<y.b;
	return x.a<y.a;
}

bool cmp2(num x,num y)
{
	if(x.b==y.b)
		return x.c<y.c;
	return x.b<y.b;
}

int lowbit(int x)
{
	return x&-x;
}

void update(int x,int y)
{
	for(;x<=k;x+=lowbit(x))	t[x]+=y;
}

int query(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=t[x];
	return res;
}

void CDQ(int l,int r)
{
	if(l==r) return ;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	int L=l;
	sort(cdq+l,cdq+mid+1,cmp2);
	sort(cdq+mid+1,cdq+r+1,cmp2);
	for(int i=mid+1;i<=r;i++)
	{
		while(L<=mid && cdq[L].b<=cdq[i].b)
		{
			update(cdq[L].c,cdq[L].sum);L++;
		}
		cdq[i].ans+=query(cdq[i].c);
	}
	for(int i=l;i<L;i++)
	{
		update(cdq[i].c,-cdq[i].sum);
	}
}

int main()
{
	n=read(),k=read();
	
	for(int i=1;i<=n;i++)
	{
		val[i].a=read();
		val[i].b=read();
		val[i].c=read();
	}
	
	sort(val+1,val+n+1,cmp1);int tot=0;
	
	for(int i=1;i<=n;i++)
	{
		tot++;
		if(val[i].a!=val[i+1].a || val[i].b!=val[i+1].b || val[i].c!=val[i+1].c)
			cdq[++m]=val[i],cdq[m].sum=tot,tot=0;
	}
	
	CDQ(1,m);
	
	for(int i=1;i<=m;i++)
		ans[cdq[i].ans+cdq[i].sum-1]+=cdq[i].sum;
	
	for(int i=0;i<n;i++)
		cout<<ans[i]<<endl;
	
	return 0;
}

天使玩偶

细节居多的CDQ,感觉题解写的无法理解就按自己想的写了写,然后样例不过,大方向还是对的,细节上处理了一下就A了(拆成左上、左下、右上、右下四个方向二维数点,然后CDQ统计左区间对右区间的影响)

(CDQ内二维数点时排序要用归并,否则会被卡常,因此果断sort+\(O_2\)

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<climits>
#include<algorithm>

using namespace std;

const int INF=1e9;
const int maxn=1e6+5;

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

int n,m,cnt;
int t[maxn],lim;
struct que
{
	int x,y,num;
	int ans,opt;
}q[maxn],Q[maxn];

int lowbit(int x)
{ return x&-x; }

bool cmp(que a,que b)
{
	if(a.x==b.x)
		return a.y<b.y;
	return a.x<b.x;
}

void clear(int x)
{ for(;x<lim;x+=lowbit(x)) t[x]=-INF; }

void update(int x,int y)
{ for(;x<lim;x+=lowbit(x)) t[x]=max(t[x],y); }

int query(int x)
{
	int res=-INF;
	for(;x;x-=lowbit(x)) res=max(res,t[x]);
	return res;
}

void CDQ(int l,int r)
{
	if(l==r) return ;
	int mid=(l+r)>>1,tot=0;
	CDQ(l,mid),CDQ(mid+1,r);
	
	for(int i=l;i<=r;i++)
	{
		if(i<=mid && q[i].opt==1)
			Q[++tot].x=q[i].x,Q[tot].y=q[i].y,Q[tot].num=i;			
		if(i>mid && q[i].opt==2)
			Q[++tot].x=q[i].x,Q[tot].y=q[i].y,Q[tot].num=i;
	}
	
	sort(Q+1,Q+tot+1,cmp);

	for(int i=1;i<=tot;i++)
	{
		if(q[Q[i].num].opt==1)
		{
			update(lim-Q[i].y,Q[i].x-Q[i].y);
		} 
		else
		{
			q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].x-Q[i].y-query(lim-Q[i].y)));
		}
	}
	
	for(int i=1;i<=tot;i++) 
		if(q[Q[i].num].opt==1) clear(lim-Q[i].y);
	
	for(int i=1;i<=tot;i++)
	{
		if(q[Q[i].num].opt==1)
		{
			update(Q[i].y,Q[i].x+Q[i].y);			
		}
		else
		{
			q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].x+Q[i].y-query(Q[i].y)));			
		}
	}
	
	for(int i=1;i<=tot;i++)
		if(q[Q[i].num].opt==1) clear(Q[i].y);
	
	for(int i=tot;i>=1;i--)
	{
		if(q[Q[i].num].opt==1)
		{
			update(lim-Q[i].y,-Q[i].x-Q[i].y);			
		}
		else
		{
			q[Q[i].num].ans=min(q[Q[i].num].ans,abs(-Q[i].x-Q[i].y-query(lim-Q[i].y)));			
		}
	}
	
	for(int i=tot;i>=1;i--)
		if(q[Q[i].num].opt==1) clear(lim-Q[i].y);
	
	for(int i=tot;i>=1;i--)
	{
		if(q[Q[i].num].opt==1)
		{
			update(Q[i].y,Q[i].y-Q[i].x);			
		}
		else
		{
			q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].y-Q[i].x-query(Q[i].y)));			
		}
	}
	
	for(int i=tot;i>=1;i--)
		if(q[Q[i].num].opt==1) clear(Q[i].y);
}

int main()
{
	n=read(),m=read();
	
	for(int i=1;i<=n;i++)
	{
		q[++cnt].opt=1;
		q[cnt].x=read();
		q[cnt].y=read()+1;
		lim=max(lim,q[cnt].y);
	}
	
	for(int i=1;i<=m;i++)
	{
		q[++cnt].opt=read();
		q[cnt].x=read();
		q[cnt].y=read()+1;
		q[cnt].ans=INF;
		lim=max(lim,q[cnt].y);
	}
	
	lim++;
	
	memset(t,0xcf,sizeof(t));

	CDQ(1,n+m);
	
	for(int i=n;i<=n+m;i++)
	{
		if(q[i].opt==2)
			cout<<q[i].ans<<'\n';
	}
	
	return 0;	
}

[CEOI 2017] Building Bridges

CDQ维护斜率优化板题,因为 \(k0\)\(x\) 均不单调,我们先按 \(k0\) sort,然后再CDQ归并使局部的 \(x\) 单调,最后单调队列转移

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

const int INF=1e18;
const int maxn=2e5+5;

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

int h[maxn];
int w[maxn];
int dp[maxn],n;
int sum[maxn];

struct node
{
	int x,y,k,id;
}nod[maxn],buc[maxn];

bool cmp(node a,node b)
{
	return a.k<b.k;
}

int q[maxn];

long double slope(int i,int j)
{
	if(nod[i].x==nod[j].x)
		return nod[j].y>nod[i].y ? INF : -INF ;
	return (long double)(nod[j].y-nod[i].y)/(nod[j].x-nod[i].x);
}

void CDQ(int l,int r)
{
	if(l==r)
	{
		int id=nod[l].id;
		nod[l].y=dp[id]+h[id]*h[id]-sum[id];
		return ;
	}
	int mid=(l+r)>>1;
	int L=l,R=mid+1,head=1,tail=0;
	for(int i=l;i<=r;i++)
	{
		if(nod[i].id<=mid)
			buc[L++]=nod[i];
		else
			buc[R++]=nod[i];
	}
	for(int i=l;i<=r;i++) nod[i]=buc[i];
	CDQ(l,mid);
	for(int i=l;i<=mid;i++)
	{
		while(head<tail && slope(q[tail-1],q[tail])>=slope(q[tail],i)) tail--;
		q[++tail]=i;
	}
	for(int i=mid+1;i<=r;i++)
	{
		while(head<tail && slope(q[head],q[head+1])<=nod[i].k) head++;
		if(head<=tail)
		{
			int id=nod[i].id,j=q[head];
			dp[id]=min(dp[id],nod[j].y-nod[i].k*nod[j].x+sum[id-1]+h[id]*h[id]);
		}
	}
	CDQ(mid+1,r);
	L=l,R=mid+1;int cnt=l;
	while(L<=mid && R<=r)
	{
		if(nod[L].x<nod[R].x)
			buc[cnt++]=nod[L++];
		else
			buc[cnt++]=nod[R++];
	}
	while(L<=mid) buc[cnt++]=nod[L++];
	while(R<=r) buc[cnt++]=nod[R++];
	for(int i=l;i<=r;i++) nod[i]=buc[i];
}

signed main()
{
	n=read();
	
	for(int i=1;i<=n;i++)
	{
		h[i]=read();
	}
	
	for(int i=1;i<=n;i++)
	{
		w[i]=read(),nod[i].id=i;
		sum[i]=sum[i-1]+w[i];dp[i]=INF;
		nod[i].x=h[i],nod[i].k=h[i]*2;
	}
	
	sort(nod+1,nod+n+1,cmp);
	
	dp[1]=0,CDQ(1,n);
	
	printf("%lld",dp[n]);
	
	return 0;
}

[CQOI 2011] 动态逆序对

把时间维度加上,然后就变成了三维偏序

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=1e5+10; 

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

int n,m,tot;
int val[maxn];
int del[maxn];
int ans[maxn];
int pos[maxn];
int t[maxn];
struct num
{
	int val,pos,id,opt;
}q[maxn*2];

bool cmp(num a,num b)
{
	return a.pos<b.pos; 
}

int lowbit(int x)
{ return x&-x; }

void update(int x,int y)
{ for(;x<=n;x+=lowbit(x)) t[x]+=y; }

int query(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=t[x];
	return res;
}

void CDQ(int l,int r)
{
	if(l==r) return ;
	int mid=(l+r)>>1,L=l;
	CDQ(l,mid),CDQ(mid+1,r);	
	sort(q+l,q+mid+1,cmp);
	sort(q+mid+1,q+r+1,cmp);
	for(int i=mid+1;i<=r;i++)
	{
		while(L<=mid && q[L].pos<=q[i].pos)
		{
			update(q[L].val,q[L].opt);L++;
		}
		ans[q[i].id]+=q[i].opt*(query(n)-query(q[i].val));
	}
	
	for(int i=l;i<L;i++)
	{
		update(q[i].val,-q[i].opt);
	}

	L=mid;
	for(int i=r;i>=mid+1;i--)
	{
		while(L>=l && q[L].pos>=q[i].pos)
		{
			update(q[L].val,q[L].opt);L--;
		}
		ans[q[i].id]+=q[i].opt*query(q[i].val-1);
	}
	
	for(int i=mid;i>L;i--)
	{
		update(q[i].val,-q[i].opt);
	}
}

signed main()
{
	n=read(),m=read();
	
	for(int i=1;i<=n;i++)
	{
		val[i]=read();
		pos[val[i]]=i;
		q[++tot].val=val[i];
		q[tot].pos=i,q[tot].opt=1;
	}
	
	for(int i=1;i<=m;i++)
	{
		del[i]=read();
		q[++tot].val=del[i];
		q[tot].pos=pos[del[i]];
		q[tot].id=i,q[tot].opt=-1;
	}
	
	CDQ(1,tot);
	
	for(int i=1;i<=m;i++)
	{
		ans[i]+=ans[i-1];
	}
	
	for(int i=0;i<m;i++)
	{
		cout<<ans[i]<<'\n';
	}
	
	return 0;
}

[HNOI 2010] 城市建设

动态最小生成树板子,在CDQ的过程中维护一下一定会在最终的最小生成树中的边有哪些,将它们缩成一个点,再维护一下不需要的边有哪些,删除这些边,从而减小图的规模,最后直接上kruskal板子就行

AC code

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#define ll long long

using namespace std;

const int LOG=20;
const int INF=1e9;
const int maxn=5e4+5;

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

int n,m,k;
int val[maxn];
int num[LOG];
int fa[maxn];
int id[maxn];
struct edge
{
	int u,v;
	int val,pos;
}e[LOG][maxn],q1[maxn],q2[maxn];

struct que
{
	ll ans;
	int pos,val;
}q[maxn];

int find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}

void merge(int x,int y)
{
	srand(time(0));
	if(rand()&1) fa[x]=y;
	else fa[y]=x;
}

bool cmp(edge a,edge b)
{
	return a.val<b.val;
}

void construction(int &tot,ll &ans)
{
	int cnt=0;
	for(int i=1;i<=tot;i++)
		fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
	sort(q1+1,q1+tot+1,cmp);
	for(int i=1;i<=tot;i++)
	{
		int u=find(q1[i].u);
		int v=find(q1[i].v);
		if(u==v) continue;
		merge(u,v);q2[++cnt]=q1[i];
	}
	for(int i=1;i<=cnt;i++)
		fa[q2[i].u]=q2[i].u,fa[q2[i].v]=q2[i].v;
	for(int i=1;i<=cnt;i++)
	{
		int u=find(q2[i].u);
		int v=find(q2[i].v);
		if(u!=v && q2[i].val!=-INF)
			merge(u,v),ans+=q2[i].val;
	}
	cnt=0;
	for(int i=1;i<=tot;i++)
	{
		int u=q1[i].u,v=q1[i].v;
		if(find(u)==find(v)) continue;
		q2[++cnt]=q1[i];id[q1[i].pos]=cnt;
		q2[cnt].u=fa[q2[cnt].u];
		q2[cnt].v=fa[q2[cnt].v];
	}
	
	for(int i=1;i<=cnt;i++) q1[i]=q2[i];
	tot=cnt;
}

void reduction(int &tot)
{
	int cnt=0;
	for(int i=1;i<=tot;i++)
		fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
	sort(q1+1,q1+tot+1,cmp);
	for(int i=1;i<=tot;i++)
	{
		int u=q1[i].u,v=q1[i].v;
		if(find(u)!=find(v))
		{
			merge(find(u),find(v));
			q2[++cnt]=q1[i],id[q1[i].pos]=cnt;			
		}
		else if(q1[i].val==INF)
			q2[++cnt]=q1[i],id[q1[i].pos]=cnt;
	}
	for(int i=1;i<=cnt;i++) q1[i]=q2[i];
	tot=cnt;
}

void CDQ(int l,int r,int dep,ll ans)
{
	int tot=num[dep];
	if(l==r) val[q[l].pos]=q[l].val;
	for(int i=1;i<=tot;i++)
		e[dep][i].val=val[e[dep][i].pos];
	for(int i=1;i<=tot;i++)
		q1[i]=e[dep][i],id[q1[i].pos]=i;
	if(l==r)
	{
		q[l].ans=ans;
		for(int i=1;i<=tot;i++)
			fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
		sort(q1+1,q1+tot+1,cmp);
		for(int i=1;i<=tot;i++)
		{
			int u=find(q1[i].u);
			int v=find(q1[i].v);
			if(u==v) continue;
			merge(u,v);q[l].ans+=q1[i].val;
		}
		return ;
	}
	
	for(int i=l;i<=r;i++) q1[id[q[i].pos]].val=-INF;
	construction(tot,ans);
	for(int i=l;i<=r;i++) q1[id[q[i].pos]].val=INF;
	reduction(tot);
	for(int i=1;i<=tot;i++)	e[dep+1][i]=q1[i];
	num[dep+1]=tot;	int mid=(l+r)>>1;
	CDQ(l,mid,dep+1,ans); CDQ(mid+1,r,dep+1,ans);
}

int main()
{
	n=read(),m=read(),k=read();
	
	for(int i=1;i<=m;i++)
	{
		e[0][i].pos=i;
		e[0][i].u=read();
		e[0][i].v=read();
		e[0][i].val=read();
		val[i]=e[0][i].val;
	}
	
	for(int i=1;i<=k;i++)
	{
		q[i].pos=read();
		q[i].val=read();
	}
	
	num[0]=m;
	CDQ(1,k,0,0);
	
	for(int i=1;i<=k;i++)
		cout<<q[i].ans<<'\n';
	
	return 0;
}
posted @ 2022-11-05 14:36  NinT_W  阅读(175)  评论(0编辑  收藏  举报