美丽的不是这个世界,而是看世界的你的眼神。

T1 你相信引力吗

解题思路

好像只有我一个人没有看出来这个题是单调栈(现在一看区间问题就是双指针,线段树)

维护一个单调递减的栈。

我们把最大值放到左端点,这样可以使一个弧不在一起的情况更加好处理。

对于当前扫到的点,一定可以和栈里小于等于这个数的点连边,还可以和大于这个数的第一个点连边。

对于相等的数,维护一下个数就好了。

然后对于最后扫完之后栈里剩下的元素,都一定是可以和最大值连边的,直接计算就好了

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e6+10;
int n,id,ans,s[N<<1],sum[N];
struct Stack
{
	int all,num[N];
	bool empty(){return all==0;}
	int top(){return num[all];}
	void push(int x){num[++all]=x;}
	void pop(){all--;}
};
Stack sta;
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)	s[i+n]=s[i]=read()+1;
	for(int i=1;i<=n;i++)	if(s[i]>s[id])	id=i;
	for(int i=id;i<=id+n-1;i++)
	{
		while(!sta.empty()&&sta.top()<s[i]){ans++;sta.pop();}
		ans+=(sta.top()>s[i])?1:sum[sta.all]+(s[id]!=s[i]);
		sum[sta.all+1]=(sta.top()==s[i])?sum[sta.all]+1:1;
		sta.push(s[i]);
	}
	while(sta.all>2&&sta.num[2]!=sta.top())	ans++,sta.pop();
	printf("%lld",ans);
	return 0;
}
T2 marshland

解题思路

果然网络流题目最大的特点就是看不出来是网络流。

本题就是最大费用可行流,把每个有危险的点拆成两个。

建图的话有四列点:

  1. 没有危险值,并且横坐标是奇数的

  2. 有危险值的入点

  3. 有危险值的出点

  4. 没有危险值,并且横坐标是偶数的

边的容量都是 1 ,除了有危险值的点自己和自己之间费用是 自身危险值 之外,其它的全部是 0 。

建图原因:因为要保证每一次只放置一个石头,因此容量为 1 。

由于 L 形状的石头放在没有危险值的部分一定是相邻的两行,一奇一偶,因此没危险值的分为两列建边。

不一定要放置 m 个,所以每跑一次就更新一次答案就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e3+10,M=5e5+10;
int n,m,K,fro,t,ans,sum,dis[N],flow[N],pre[N],las[N],s[60][60],mincost;
int tot=1,head[N],nxt[M<<1],ver[M<<1],edge[M<<1],val[M<<1];
int d1[6]={0,1,0,0,-1};
int d2[6]={0,0,-1,1,0};
bool vis[N];
struct Queue
{
	int l,r,num[M];
	void clear(){l=1;r=0;}
	void push(int x){num[++r]=x;}
	void pop(){l++;}
	int front(){return num[l];}
	bool empty(){return l>r;}
};
Queue q;
void add_edge(int x,int y,int ed,int va)
{
	ver[++tot]=y;
	edge[tot]=ed;
	val[tot]=va;
	nxt[tot]=head[x];
	head[x]=tot;
}
void add(int x,int y,int va)
{
	add_edge(x,y,1,va);
	add_edge(y,x,0,-va);
}
bool SPFA()
{
	memset(dis,-0x3f,sizeof(dis));
	memset(flow,0x3f,sizeof(flow));
	q.clear();	
	q.push(fro);
	dis[fro]=0;	vis[fro]=true;
	pre[t]=-1;
	while(!q.empty())
	{
		int x=q.front();
		vis[x]=false;	q.pop();
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i];
			if(edge[i]>0&&dis[to]<dis[x]+val[i])
			{
				dis[to]=dis[x]+val[i];
				flow[to]=min(flow[x],edge[i]);
				pre[to]=x;	las[to]=i;
				if(!vis[to])	q.push(to),vis[to]=true;
			}
		}
	}
	return pre[t]!=-1;
}
void MCMF()
{
	int all=1;
	while(all<=m&&SPFA())
	{
		int x=t;	all++;
		mincost+=flow[t]*dis[t];
		ans=max(ans,mincost);
		while(x!=fro)
		{
			edge[las[x]]-=flow[t];
			edge[las[x]^1]+=flow[t];
			x=pre[x];
		}
	}
}
signed main()
{
	n=read();	m=read();	K=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			s[i][j]=read();
			sum+=s[i][j];
		}
	for(int i=1,x,y;i<=K;i++)
	{
		x=read();	y=read();
		s[x][y]=-1;
	}
	fro=2*n*n+1;	t=2*n*n+2;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if((i+j)&1)
			{
				if(s[i][j]==-1)	continue;
				add((i-1)*n+j,n*n+(i-1)*n+j,s[i][j]);
				for(int k=1;k<=4;k++)
				{
					int x=i+d1[k],y=j+d2[k];
					if(x<=0||y<=0||x>n||y>n)	continue;
					if(s[x][y]==-1)	continue;
					if(x&1)	add((x-1)*n+y,(i-1)*n+j,0);
					else	add(n*n+(i-1)*n+j,n*n+(x-1)*n+y,0);
				}
			}
			else
			{
				if(s[i][j]==-1)	continue;
				if(i&1)	add(fro,(i-1)*n+j,0);
				else	add(n*n+(i-1)*n+j,t,0);
			}
	MCMF();
	printf("%lld",sum-ans);
	return 0;
}
T3 party?

解题思路

暴力跳的话有 86 pts的巨额分数。(TLE的点是链)

主要是一个 Hall定理

对于一个二分图 \(X\)\(Y\),并且\(|X|=|Y|\),设点集 \(S\) 向另一侧的连边并集为 \(M(S)\) ,那么对于 \(X\) 的任何子集都有 \(|S|\le|M(S)|\),这是满足二分图存在完美匹配的充要条件。

不难发现其实人就是 X,Y 就是特产,并且一定满足上述条件。

因此对于每一个询问我们只需要枚举人们的各种组合方案,然后对于答案取个 min 。

接下来考虑如何快速求出每个人所可以携带的特产。

不难发现其实每个城市的连边就构成了一棵类似于树的结构。

因此可以用树剖+线段树维护一个 bitset 。

code

86pts暴力

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=3e5+10,M=5e4+10,INF=1e18;
int n,m,qus,p[N],s[N],res[6];
bitset<1010> bit[10],all;
bool flag1,flag2;
struct Ques
{
	int c,dat[6];
}q[M];
bool check(int x,int cnt,int ov)
{
	for(int i=1;i<=cnt;i++)
		if(res[i]<x)
			ov-=x-res[i];
	return ov>=0;
}
signed main()
{
	n=read();	m=read();	qus=read();
	if(!qus)	return 0;
	for(int i=2;i<=n;i++)
	{
		p[i]=read();
		if(p[i]!=i-1)	flag2=true;
	}
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int i=1;i<=qus;i++)
	{
		q[i].c=read();
		if(q[i].c!=2)	flag1=true;
		for(int j=1;j<=q[i].c;j++)
			q[i].dat[j]=read();
	}
	for(int i=1;i<=qus;i++)
	{
		int cnt=q[i].c,minn=INF,ans=m;
		for(int j=1;j<=cnt;j++)
			bit[j].reset();
		for(int j=1;j<=cnt;j++)
			minn=min(minn,q[i].dat[j]);
		while(1)
		{
			for(int j=1;j<=cnt;j++)
				while(q[i].dat[j]>minn)
				{
					bit[j][s[q[i].dat[j]]]=true;
					q[i].dat[j]=p[q[i].dat[j]];
				}
			int pre=minn;
			for(int j=1;j<=cnt;j++)
				minn=min(minn,q[i].dat[j]);
			if(pre==minn) break;
		}
		for(int j=1;j<=cnt;j++)
			while(q[i].dat[j]>minn)
			{
				bit[j][s[q[i].dat[j]]]=true;
				q[i].dat[j]=p[q[i].dat[j]];
			}
		for(int j=1;j<=cnt;j++)
			bit[j][s[q[i].dat[j]]]=true;
		for(int sta=1;sta<(1<<cnt);sta++)
		{
			int sum=0;	all.reset();
			for(int j=1;j<=cnt;j++)
				if((sta>>j-1)&1)
					all|=bit[j],sum++;
			ans=min(ans,(int)all.count()/sum);
		}
		printf("%lld\n",ans*cnt);
	}
	return 0;
}

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=3e5+10,M=1e3+10,INF=1e18;
int n,m,q,cnt,pos[6],s[N];
int tim,dfn[N],id[N],siz[N],fa[N],topp[N],son[N],dep[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
bitset<M> tre[N<<2],pre[N],all,bit[6];
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs1(int x)
{
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		dep[to]=dep[x]+1;
		fa[to]=x;
		dfs1(to);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]])
			son[x]=to;
	}
}
void dfs2(int x,int tp)
{
	dfn[x]=++tim;
	id[tim]=x;
	topp[x]=tp;
	if(son[x])	dfs2(son[x],tp);
	for(int i=head[x];i;i=nxt[i])
		if(!dfn[ver[i]])
			dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
	if(!x||!y)	return 0;
	while(topp[x]^topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])
			swap(x,y);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return x;
}
void dfs3(int x)
{
	if(topp[x]==x)	pre[x][s[x]]=true;
	else	pre[x]=pre[fa[x]],pre[x][s[x]]=true;
	for(int i=head[x];i;i=nxt[i])
		dfs3(ver[i]);
}
void push_up(int x)
{
	tre[x]=tre[ls]|tre[rs];
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		tre[x][s[id[l]]]=true;
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(x);
}
bitset<M> query(int x,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)	return tre[x];
	int mid=(l+r)>>1;
	bitset<M> temp;	temp.reset();
	if(L<=mid)	temp=query(ls,l,mid,L,R);
	if(R>mid)	temp|=query(rs,mid+1,r,L,R);
	return temp;
}
bitset<M> query(int x,int y)
{
	bitset<M> temp;	temp.reset();
	while(topp[x]^topp[y])
	{
		temp|=pre[x];
		x=fa[topp[x]];
	}
	temp|=query(1,1,tim,dfn[y],dfn[x]);
	return temp;
}
signed main()
{
	n=read();	m=read();	q=read();
	for(int i=2,x;i<=n;i++)
		x=read(),add_edge(x,i);
	for(int i=1;i<=n;i++)
		s[i]=read();
	dfs1(1);	dfs2(1,1);
	dfs3(1);	build(1,1,tim);
	while(q--)
	{
		cnt=read();
		for(int i=1;i<=cnt;i++)
			pos[i]=read();
		int lca=pos[1],ans=m;
		for(int i=1;i<=cnt;i++)
			lca=LCA(lca,pos[i]);
		for(int i=1;i<=cnt;i++)
			bit[i]=query(pos[i],lca);
		for(int sta=1;sta<(1<<cnt);sta++)
		{
			int sum=0;	all.reset();
			for(int i=1;i<=cnt;i++)
				if((sta>>i-1)&1)
					all|=bit[i],sum++;
			ans=min(ans,(int)all.count()/sum);
		}
		printf("%lld\n",ans*cnt);
	}
	return 0;
}
T4 半夜

解题思路

记 DP 数组\(f_{i,j,k}=\text{LCS}(S[i,j],T[1,k])\)

不难发现有如下性质:

  • \(f_{i-1,j,k}>f_{i-1,j-1,k}\Rightarrow f_{i,j,k}>f_{i,j-1,k}\)

  • \(f_{i,j,k}>f_{i,j,k-1}\Rightarrow f_{i-1,j,k}>f_{i-1,j,k-1}\)

那么就必然存在分界点 \(p_{k,j}\)\(q_{k,j}\) 满足:

\[f_{i,j,k}=f_{i,j-1,k}+[i\ge p_{k,j}]=f_{i,j,k-1}+[i<q_{k,j}] \]

\(F=f_{i,j-1,k-1}\)

就可以发现如下关系。

8.16考试总结(NOIP模拟41)[你相信引力吗·marshland·party?·半夜]_#include

然后,我们就可以进而推出 p 与 q 。。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
const int N=2e3+10;
int n,ans,p[N][N<<1],q[N][N<<1];
char ch1[N],ch2[N<<1];
signed main()
{
	scanf("%lld%s%s",&n,ch1+1,ch2+1);
	for(int i=1;i<=n;i++)
		ch2[i+n]=ch2[i];
	for(int i=1;i<=2*n;i++)
		p[0][i]=i;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=2*n;j++)
		{
			p[i][j]=p[i-1][j];	q[i][j]=q[i][j-1];
			if(ch1[i]==ch2[j]||p[i][j]<=q[i][j])	swap(p[i][j],q[i][j]);
		}
	for(int i=1,sum;i<=n;i++)
	{
		sum=0;
		for(int j=i;j<i+n;j++)
			sum+=(i>p[n][j]);
		ans=max(ans,sum);
	}
	printf("%lld",ans);
	return 0;
}