其他分享
首页 > 其他分享> > NOI2018题解

NOI2018题解

作者:互联网

D1T1

洛谷题目传送门

题目描述

给定一个n个点m条边的无向图,每条边有高度和长度,Q次询问,每次给定起点,以及限制高度,求从起点能通过高度大于限制高度的边到达的点中,到1号点最短路的最小值
强制在线

65pts 不强制在线

把边权和询问的权值都排序,用并查集维护连通块内到1号点距离最小的点

100pts

解法一

在65pts上改进
我们只需要把用并查集合并的过程可持久化,就能访问任何一个版本的并查集
就可以做到在线了
若果使用按秩合并的并查集\(O(nlog^2n)\)

解法二

建出Kruskal重构树
那么可以到达的点就是一个子树,用树上倍增维护最远能走到那个点
预处理每个点的子树内到1号店距离最近的点即可
时间复杂度都是\(O(nlogn)\),且细节比较少,空间消耗小

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+3;
struct P
{
	int id;
	LL v;
};
bool operator <(const P &x,const P &y)
{
	return x.v>y.v;
}
priority_queue<P> q;
struct node
{
	int y,next,l;
}e[4*N];
struct edge
{
	int x,y,h;
}E[2*N]; 
int link[N],t;
int n,m,Q,K,s;
void Insert(int x,int y,int l)
{
	e[++t].y=y;
	e[t].l=l;
	e[t].next=link[x];
	link[x]=t;
}
bool cmp(edge a,edge b)
{
	return a.h>b.h;
}
int read(){
	int ans=0,op=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') op=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){ans=(ans<<1)+(ans<<3)+ch-'0';ch=getchar();}
	return ans*op;
}
bool vis[N];
LL dis[N];
P Pc(int x,LL v)
{
	P tem;
	tem.id=x;
	tem.v=v;
	return tem; 
} 
void dijistra()
{
	for(int i=1;i<=n;i++)
	{
		vis[i]=0;
		dis[i]=1e15;
	}
	dis[1]=0;
	q.push(Pc(1,0));
	while(!q.empty())
	{
		P tp=q.top();q.pop();
		int x=tp.id;
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=link[x];i;i=e[i].next)
		{
			int y=e[i].y;
//			cout<<x<<' '<<y<<' '<<dis[x]<<' '<<e[i].l<<' '<<dis[y]<<endl;
			if(dis[x]+e[i].l<dis[y])
			{
				dis[y]=dis[x]+e[i].l;
				q.push(Pc(y,dis[y])); 
			}
		}
	}
}	
int cnt,f[2*N],val[2*N],Min[2*N];
int Find(int x)
{
	if(x==f[x]) return x;
	return f[x]=Find(f[x]);
}
int up[2*N][30];
void kruskal()
{
	for(int i=1;i<=n;i++)
	{
		Min[i]=dis[i];	
		f[i]=i;
	}
	sort(E+1,E+m+1,cmp);
	memset(link,0,sizeof(link));
	t=0;
	for(int i=1;i<=m;i++)
	{
		int x=E[i].x;
		int y=E[i].y;
		int fx=Find(x),fy=Find(y);
		if(fx==fy) continue;
		cnt++;
		val[cnt]=E[i].h;
		Min[cnt]=min(Min[fx],Min[fy]);
		f[fx]=cnt;f[cnt]=cnt;f[fy]=cnt;
		up[fx][0]=cnt;
		up[fy][0]=cnt;
		Insert(cnt,fx,0);
		Insert(cnt,fy,0);
	}
}

int main()
{
	freopen("return.in","r",stdin);
	freopen("return.out","w",stdout);
	int T;
	cin>>T;
	while(T--)
	{		
		t=0;

		memset(link,0,sizeof(link));
		memset(vis,0,sizeof(vis));
		memset(Min,89,sizeof(Min));
		memset(up,0,sizeof(up));
		n=read();m=read();		cnt=n;
		for(int i=1;i<=m;i++)
		{
			int x,y,l,h;
			x=read();
			y=read();
			l=read();
			h=read();
			Insert(x,y,l);
			Insert(y,x,l);
			E[i].x=x;
			E[i].y=y;
			E[i].h=h;
		}
		dijistra();
		kruskal();
		for(int k=1;(1<<k)<=cnt;k++) 
			for(int i=1;i<=cnt;i++) 
				up[i][k]=up[up[i][k-1]][k-1];
		Q=read();K=read();s=read();
		LL last=0;
		while(Q--)
		{
			int v0,h0;
			v0=read();
			h0=read();
			int v=(v0+K*last-1)%n+1;
			int h=(h0+K*last)%(s+1); 
			for(int k=22;k>=0;k--)
				if(up[v][k]&&val[up[v][k]]>h) 
					v=up[v][k];
			last=Min[v];
			printf("%lld\n",Min[v]);
		}		
	} 

	return 0;
}

附赠Kruskal重构树三道题
BZOJ3545Peaks
CF1408G
[IOI2018] werewolf 狼人

D1T2

洛谷题目传送门

8pts

n!枚举排列,然后判断即可

44pts

首先考虑什么样的序列是符合条件的
题目提示启发我们,可以考虑每个元素的移动
因为总交换次数是要达到下界,所以每一次交换都必须是有益的
考虑如果排列第i个位置是p[i]
若\(p[i]=i\),则这位置不能被交换
若\(p[i]<i\),则这个数需要往左移动,且不能向右移动
那么不能存在一个j满足,\(j>i,p[j]<p[i]\),否则这两个位置一定会交换一次,那么\(p[i]\)就向相反方向移动了,答案一定会变大,即\(i\)右边的数都比\(p[i]\)大
若\(p[i]>i\),类似的可以得到i左边的数都比\(p[i]\)小
且这三个限制和原限制是等价的
直接状压选了那些数字即可
字典序的限制可以类似数位dp去搞
复杂度\(O(2^nn)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int n,a[N];
LL f[1<<20][2];
void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	int m=(1<<n);
	memset(f,0,sizeof(f));
	f[0][1]=1;
	for(int s=0;s<m-1;s++)
	{
		int p=0;
		int a1=0,a2=n+1;
		for(int i=1;i<=n;i++)
		if((s>>(i-1))&1)
		{
			p++;
			a1=max(a1,i);
		}
		else a2=min(a2,i);
		p++;
		for(int limit=0;limit<=1;limit++)
		{
			int up=(limit?a[p]:1);
			for(int i=up;i<=n;i++)
			{
				int k=(limit&&(i==up));
				if(!((s>>(i-1))&1))
				{
					if(i>=p)
					{
						if(a1<i)
						f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
					}
					else
					{
						if(i<=a2)
						f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
					}
				}
			}
		}
	}
	printf("%lld\n",f[m-1][0]);
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

80pts

一个重要的结论:
不考虑字典序限制
题目所求等价于求有多少个排列,满足排列中不存在长度大于等于3的下降子序列

必要性:

即若该排列合法,则不存在长度大于等于3的下降子序列
若不然,则存在三个数\(i,j,k\)满足,\(i<j<k,p[i]>p[j]>p[k]\)
若\(j<p[j]\),由上文可知,j左边的数都比j小,与\(p[i]>p[i]\)矛盾
若\(j>p[j]\),则j右边的数都比j大,与\(p[j]>p[k]\)矛盾
所以矛盾,即原命题成立
充分性

即若不存在\(p[i]>p[j]>p[k]\),则性质成立
考虑一次交换,交换了\(p[i],p[i+1]\)
则由题可知,\(p[i]>p[i+1]\)
又因为没有长度大于等于3的下降子序列,所以i左边的数都小于\(p[i]\)
那么就有\(p[1……i-1]\)和\(p[i+1]\)总共i个数比\(p[i]\)小,即\(p[i]>i\)
类似的我们可以得到\(p[i+1]<i+1\),那么这次交换对最终排列而言是合法的
所以命题成立
即题目等价于求有多少的排列,满足排列中不存在长度大于等于3的下降子序列

考虑dp
设\(dp[i][j]\)表示有多少个长度为i的排列,满足第一个元素是j,且不存在长度大于等于3的下降子序列,则由定义知\(j\leq i\)
考虑第二个数填k
若\(j<=k\),则加上dp[i-1][k],这里之所以能等于j,而不会重复,是因为虽然整个序列是一个排列,但是每一个子段并不是一个排列,而我们之所以能转移,是因为我们只考虑元素的相对关系,即离散化之后的值,那么长度为i的数列,长度为i-1的数列,离散化之后有值相等是显然可以的
若\(1<k<j\),那么一定会存在一个\(j,k,1\)的不合法,下降子序列,因此贡献为0
若\(k==1\)
贡献显然并不是\(dp[i-1][1]\)
首先1~j-1一定是升序排列的,否则就会存在不合法的下降子序列
若不合法,则存在\(x<y,j>p[x]>p[y]>1\)
考虑一个映射:
把x移到x+1的位置上,特别的让j-1移到1的位置上
则新序列和原序列是一一对应的,即是一个双双射
且新数列中存在\((j-1,p[x]-1,p[y]-1)\)的下降子序列
所以若填的k等于1,则等价于不存在以j-1开始的长度为3的下降子序列,即dp[i-1][j-1]
综合三种情况
\(dp[i][j]=\sum_{k=j-1}^{i-1}dp[i-1][k]\)

考虑字典序的限制
枚举与给定排列的最长公共前缀i
设前缀最大值为x,满足\(p[j]<=x,j>=i\)的j的个数为s
那么离散化之后,x就是s
又因为有字典序的限制,所以填的数字应该>p[i],也就是说,当前位置可以填的最小的数是s+1,最大的是n-i+1,当然是离散化之后的
即答案是

\[Ans=\sum_{i=1}^{n-1}\sum_{k=s+1}^{n-i+1}dp[n-i+1][k] \]

预处理dp值,通过前缀和优化,复杂度\(O(n^2)\)
因为代码很简单,所以略

100pts

观察到答案实际上若干dp数组的后缀和
设\(s(n,m)=\sum_{i=m}^ndp[n][i]\),其中\(m\leq n\)

\[s(n,m)=\sum_{i=m+1}^ndp[n][i]+dp[n][m] \]

\[=\sum_{i=m+1}^ndp[n][i]+\sum_{i=m-1}^{n-1}dp[n-1][i] \]

\[=s(n,m+1)+s(n-1,m-1) \]

考虑组合意义,
在平面直角坐标系上从\((0,0)\)走到\((n,m)\)每次向下走一步或者向右上走一步的方案数
并且不能超过\(y=x\)这条线和x轴之下
首先仅通过这两种走法,随便走,不可能越过\(y=x\),但有可能越过x轴
因为只有第二种会向右走一步,所以一共有n次第二种操作,也可以得到有n-m次第一种操作
为了让这两种操作相似,不妨把想下,改为向右
则原先走到(n,m),新的操作就会走到(2n-m,m),因为多向右走了n-m次
即,每次向右上或者右下走一步,走到(2n-m,m)的方案数,且不能超过x轴
这和原问题也是双射
因为不能超过x轴,所以每个前缀里,向上走的次数要大于等于向下走的次数
这就是卡特兰数的变种问题,方案数为\(s(n,m)=C_{2n-m}^{n-m}-C_{2n-m}^{n-m-1}\)

\[Ans=\sum_{i=1}^{n-1}s(n-i+1,s+1) \]

预处理组合数,用树状数组求s
复杂度\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int c[N];
int n;
void add(int x,int v)
{
	for(int i=x;i<=n;i+=i&-i)
	c[i]+=v;
}
int ask(int x)
{
	int res=0;
	for(int i=x;i;i-=i&-i)
	res+=c[i];
	return res;
}
int a[N];
LL fac[N*2],inv[N*2];
LL M = N*2-2;
LL Pow(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int pre[N],suf[N];
int leq[N];
LL C(int n,int m)
{
	if(n<m) return 0;
	return (LL)fac[n]*inv[m]%mod*inv[n-m]%mod;
}
LL S(int n,int m)
{
	return (C(2*n-m,n-m)-C(2*n-m,n-m-1)+mod)%mod;
}
void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	pre[i]=max(pre[i-1],a[i]);
	suf[n+1]=n+1;
	for(int i=n;i>=1;i--)
	suf[i]=min(suf[i+1],a[i]);
	for(int i=n;i>=1;i--)
	{
		add(a[i],1);
		leq[i]=ask(pre[i]);
	} 
	for(int i=1;i<=n;i++)
	add(a[i],-1);
	int Max=0;
	LL ans=0;
	for(int i=1;i<=n;i++)
	{
		if(Max>suf[i]) break;
		if(leq[i]+1<=n-i+1)
		ans=(ans+S(n-i+1,leq[i]+1))%mod;
		if(a[i]<pre[i]) Max=max(Max,a[i]);
	}
	printf("%lld\n",ans);
}
int main()
{
	int T;
	cin>>T;
	fac[0]=1;
	for(int i=1;i<=M;i++)
	fac[i]=(LL)fac[i-1]*i%mod;
	inv[M]=Pow(fac[M],mod-2);
	for(int i=M-1;i>=0;i--)
	inv[i]=(LL)inv[i+1]*(i+1)%mod;
	while(T--)
	{
		solve();
	}
	return 0;
}

D2T1

首先使用muiltiset可以方便的维护出每个龙使用的是哪一把剑
因为题目要求x恰好让血量变为0,多了少了都不行
所以
等价于求方程组

\[ \left\{ \begin{matrix} c_1x\equiv a_1(mod\;p_1) \\ c_2x\equiv a_2(mod\;p_2) \\ ……\\ c_nx\equiv a_n(mod\;p_n) \end{matrix} \right. \]

的通解x
因为模数不互质,所以选择exCRT求解
但时x有系数,考虑化为不带系数

\[c_ix\equiv a_i(mod\;p_i) \]

等价于

\[c_ix+p_iy=a_i \]

求出一个特解\(x_0\)
那么\(x\)的通解可化为

\[X=x0+k\times\frac{p_i}{gcd(p_i,c_i)} \]

同时模\(\frac{p_i}{gcd(p_i,c_i)}\)
即可得到

\[X\equiv x0(mod\;\frac{p_i}{gcd(p_i,c_i)}) \]

这就化成了一般形式
使用exCRT求解即可

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5+7;
LL gcd(LL a,LL b)
{
	if(!b) return a;
	return gcd(b,a%b);
}
LL lcm(LL a,LL b)
{
	return a/gcd(a,b)*b;
}
LL exgcd(LL a,LL b,LL&x,LL &y)
{
	if(!b)
	{
		x=1;
		y=0;
		return a;
	}
	LL d=exgcd(b,a%b,x,y);
	LL z=x;
	x=y;
	y=z-(a/b)*y;
	return d;
}
LL mul(LL a,LL b,LL mod)
{
	LL res=0;
	while(b)
	{
		if(b&1) res=(res+a)%mod;
		a=(a+a)%mod;
		b>>=1; 
	}
	return res;
}
LL Ans(LL a,LL b,LL m)
{
	LL x,y;
	LL d=exgcd(a,m,x,y);
	if(b%d!=0) return -1;
	LL t=m/d;
	x=mul(x,b/d,t);
	return (x%t+t)%t;
}
LL c[N],a[N],p[N];
LL ceil(LL a,LL b)
{
	if(a%b==0) return a/b;
	return a/b+1;
}
LL search(LL a,LL b,LL c)
{
	if(a>=b) return a;
	return a+1ll*ceil(b-a,c)*c;
}
LL EXCRT(int n,LL Base)
{
	LL x=Ans(c[1],a[1],p[1]),m=p[1]/gcd(p[1],c[1]);
	if(x==-1) return -1;
	for(int i=2;i<=n;i++)
	{
		LL t=Ans(mul(c[i],m,p[i]),((a[i]-mul(c[i],x,p[i]))%p[i]+p[i])%p[i],p[i]);
		if(t==-1) return -1;
		LL nm=lcm(m,p[i]/gcd(c[i],p[i]));
		x=(x%nm+mul(t,m,nm))%nm;
		m=nm;
	}
	return search(x,Base,m);
}
LL w[N];
multiset<LL> sward;
multiset<LL>::iterator it;
void solve()
{
	int n,m;
	scanf("%d %d",&n,&m);
	sward.clear();
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	scanf("%lld",&p[i]);
	for(int i=1;i<=n;i++)
	scanf("%lld",&w[i]);
	for(int i=1;i<=m;i++)
	{
		LL s;
		scanf("%lld",&s);
		sward.insert(s);
	}
	for(int i=1;i<=n;i++)
	{
		it=sward.begin();
		if(a[i]<(*it)) c[i]=(*it);
		else
		{
			it=sward.upper_bound(a[i]);
			it--;
			c[i]=(*it);
		}
		sward.erase(it);
		sward.insert(w[i]);
	}
	LL Base=0;
	for(int i=1;i<=n;i++)
	{
		Base=max(Base,ceil(a[i],c[i]));	
	}
	for(int i=1;i<=n;i++)
	c[i]%=p[i],a[i]%=p[i];
	printf("%lld\n",EXCRT(n,Base));
}

int main()
{
//	freopen("P477_5.in","r",stdin);
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

D2T2

先咕了

D2T3

只会50pts

20pts \(n\leq 20\)

暴力建出图,然后用状压跑哈密顿回路计数

15pts A,K=1

答案只可能是0,1,2,三者之一
跑dfs即可搜出来

30pts K=1

考虑dp
设\(f[x][0/1/2]\)
分别表示,以x为根的子树,走完的方案数
1表示从x一直走到最靠左的节点,然后再走完整棵树从最右边出去的方案数
2表示从x一直走到最靠右的节点,然后再走完整棵数从最左边出去的方案数
0表示从从一边叶子进去,走完整棵数从另一端的叶子出去的方案数
转移
设son[1……len]分别是x按照dfs序排列的儿子

\[f[x][1]=f[son[x][1]]*\prod_{i=2}^{len}f[son[x]][0] \]

2,0都是类似的,看代码应该能看懂
总共50pts

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
typedef long long LL;
const int mod = 998244353;
vector<int> son[N];
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
LL f[N][4],ans=0;
void dfs(int x,int pre)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==pre) continue;
		son[x].push_back(y);
	}
	sort(son[x].begin(),son[x].end());
	for(auto y:son[x])
	dfs(y,x);
	int len=(int)son[x].size()-1;
	if(len==-1)
	{
		f[x][0]=1;
		f[x][1]=1;
		f[x][2]=1;
		return;
	}
	LL res=1;
	for(int i=1;i<=len;i++)
	res=res*f[son[x][i]][0]%mod;
	f[x][1]=f[son[x][0]][1]*res%mod;
	res=1;
	for(int i=0;i<len;i++)
	res=res*f[son[x][i]][0]%mod;
	f[x][2]=f[son[x][len]][2]*res%mod;
	if(len>=1)
	{
		for(int i=0;i<len;i++)
		{
			res=1;
			for(int j=0;j<i;j++) res=res*f[son[x][j]][0]%mod;
			for(int j=i+2;j<=len;j++) res=res*f[son[x][j]][0]%mod;
			res=res*f[son[x][i]][2]%mod;
			res=res*f[son[x][i+1]][1]%mod;
			f[x][0]=(f[x][0]+res)%mod; 
		}
	}
	if(x==1&&len>=1)
	{
		ans=1;
		ans=f[son[x][0]][1]*f[son[x][len]][2]%mod;
		for(int i=1;i<len;i++)
		ans=ans*f[son[x][i]][0]%mod; 
	}
}
int n,k;
int seq[N];
int tot=0;
void get(int x,int pre)
{
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==pre) continue;
		son[x].push_back(y);
	}
	sort(son[x].begin(),son[x].end());
	if((int)son[x].size()==0)
	{
		seq[++tot]=x;
		return;
	} 
	for(auto y:son[x])
	get(y,x);
}
LL F[1<<21][21];
void put(int S)
{
	for(int i=1;i<=n;i++)
	if((S>>(i-1))&1)
	{
		cout<<i<<"--->";
	}
	cout<<"=="<<endl;
}
LL Pow(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		int u;
		scanf("%d",&u);
		add(u,i+1);
		add(i+1,u);
	}
	if(k==1)
	{
		dfs(1,0);
		printf("%lld\n",(ans+f[1][0])%mod);
		return 0;
	}
	get(1,0);
	for(int i=1;i<=tot;i++)
	{
		for(int j=i+1;j<=tot;j++)
		{
			if(min(j-i,tot-(j-i))<=k)
			{
				add(seq[i],seq[j]);
				add(seq[j],seq[i]);
			}
		}
	}
	F[1][1]=1;
	int s=(1<<n);
	for(int S=1;S<s;S++)
	{
		for(int x=1;x<=n;x++)
		{
			if(!((S>>(x-1))&1)) continue;
			if(!F[S][x]) continue;
			for(int i=link[x];i;i=e[i].next)
			{
				int y=e[i].y;
				if(((S>>(y-1))&1)) continue;
				F[S+(1<<(y-1))][y]=(F[S+(1<<(y-1))][y]+F[S][x])%mod;
			}
		}
	}
	LL res=0;
	for(int x=2;x<=n;x++)
	{
		bool f=0;
		for(int i=link[x];i;i=e[i].next)
		{
			int y=e[i].y;
			if(y==1)
			{
				f=1;
				break;
			}
		}
		if(f==1) res=(res+F[s-1][x])%mod;
	}
	cout<<res*Pow(2,mod-2)%mod;
	return 0;
}

标签:return,int,题解,LL,son,NOI2018,dp,mod
来源: https://www.cnblogs.com/jesoyizexry/p/16221879.html