2023.3.2 日寄

2023.3.2 日寄

\(~~~~\) 希望这不是退役前最后一篇日寄,毕竟明天是不可能写日寄的。

一言

\(~~~~\) 我的北京到了,你的伦敦也到了,浮天沧海远,万里眼中明——我煮酒,等着你回来赋诗。——简媜

杂题选做

A Perfect Problem

题意

\(~~~~\) 定义一个序列是完美的当且仅当它的所有非空子序列 \(\{a_n\}\)满足:\(min\{a_n\} \times max\{a_n\} \geq \sum a_i\)。求长为 \(n\) ,值域在 \([1,n]\) 的序列有多少是完美的。
\(~~~~\) \(1\leq n\leq 200\).

题解

\(~~~~\) 来找一些性质,比如说:我们把原序列排序,那么排序后的所有子区间合法等价于原来的所有子序列合法。因为显然去掉子区间非端点的部分和那和变小但是最值的积不变。

\(~~~~\) 进一步地,我们发现左端点可以往左移到 \(1\),那也就是所有前缀合法即可,原因也显然。

\(~~~~\) 那现在我们就有了 \(\mathcal{O(n)}\) 判定的方法,也就有了 \(dp\) 的雏形:\(dp_{i,j}\) 表示当前考虑数字 \(i\) 放到序列的第 \(j\) 个数,后面可以再附带上当前的和,然后你就获得了优秀的 \(\mathcal{O(n^5)}\) dp.

\(~~~~\) 对于值域来做点优化,我们发现第 \(n\) 位最终的值要么是 \(n\) 要么是 \(n+1\) ,且为 \(n\) 的时候前面全是 \(n\)。同理你也会发现第 \(i\) 位的值 \(\geq i\) ,证明:若 \(a_i< i\) ,则 \(a_1\times a_i<a_1\times i=\sum_{j=1}^i a_1\leq \sum_{j=1}^i a_j\) ,显然不满足题意。

\(~~~~\) 进一步地,我们打表还会发现 \(a_i\) 只能取 \(n+1-\sqrt{n}\) 级别以上的数。结合以上一堆结论我们直接记搜就好了。

代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int n,MOD,Fac[505],Inv[505];
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a); 
	}
	return ret;
}
int A1;
int dp[505][505][20];
int dfs(int Num,int Sum,int Val,int Up)
{
	if(~dp[Num][Sum][Val]) return dp[Num][Sum][Val];
	int &res=dp[Num][Sum][Val]; res=0;
	if(Val<=Up-1&&Num<Val) return 0;
	if(Val==Up) return res=Num<n?Inv[n-Num]:0;
	int x=Up-Val;
	for(int j=0;Num+j<=n&&Sum+j*x<=A1;j++) res=Add(res,Mul(Inv[j],dfs(Num+j,Sum+j*x,Val+1,Up)));
	return res;
}
int main() {
	read(n);read(MOD);
	Fac[0]=1;
	for(int i=1;i<=n;i++) Fac[i]=Mul(Fac[i-1],i);
	Inv[n]=qpow(Fac[n],MOD-2);
	for(int i=n-1;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
	
	int Ans=0;
	for(int Del=0;Del<=18;Del++)
	{
		memset(dp,-1,sizeof(dp));
		A1=n+1-Del;
		Ans=Add(Ans,dfs(0,0,0,Del));	
	}
	printf("%d",Mul(Ans,Fac[n]));
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

Tourist Reform

题意

\(~~~~\) 给一张初始联通的无向图的边定向,使得 \(r_i\) 的最小值最大。其中 \(r_i\) 指的是从 \(i\) 出发能到达的点数。要求给出点数和构造方案。
\(~~~~\) \(1\leq n\leq 4\times 10^5,1\leq m\leq 4\times 10^5\).

题解

\(~~~~\) 我们来考虑这题SPJ怎么写?是不是先Tarjan缩点,然后我们发现最小值一定取自那些缩点后没有出边的点。

\(~~~~\) 那反过来我们最大化这个值即可。注意到无向图中边双一定能被定向为强联通分量,那我们把原图先边双缩点成一棵树,然后树上的边必然贡献 \(tot-1\) 个出边,所以我们只能让那个点数最多的点双成为答案,那以它为根往下搜,让边都指向根即可。

\(~~~~\) 边双内怎么定向?注意到无向图的dfs树上没有横叉边,那以任何一个点为根,树边都指向叶子,返租边都指向祖先即可。

代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
bool vis[400005];
int dfn[400005],low[400005],Times,tot,Siz[400005],belong[400005];
stack<int>S;
struct Edge{
	int u,v,dir;
	Edge(){dir=0;}
	Edge(int U,int V){u=U,v=V;}
}E[400005];
vector<PII>G[400005];
void Tarjan(int u)
{
	dfn[u]=low[u]=++Times;S.push(u);
	for(int i=0;i<(int)G[u].size();i++)
	{
		int v=G[u][i].first,id=G[u][i].second;
		if(vis[id]) continue; vis[id]=true;
		if(!dfn[v]) Tarjan(v);
		low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u])
	{
		tot++;int t=-1;
		while(t!=u)
		{
			t=S.top();S.pop();
			belong[t]=tot;
			Siz[tot]++;
		}
	}
}
void dfs(int u,int fa)
{
	for(int i=0;i<(int)G[u].size();i++)
	{
		int v=G[u][i].first,id=G[u][i].second;
		if(v==fa) continue;
		E[id].dir=(belong[E[id].u]==v?1:-1);
		dfs(v,u);
	}
}
int dep[400005];
void dfs2(int u,int fa)
{
	dep[u]=dep[fa]+1; vis[u]=true;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i].first,id=G[u][i].second;
		if(v==fa||belong[v]!=belong[u]) continue;
		if(!dep[v]) E[id].dir=(v==E[id].u?-1:1),dfs2(v,u);
		else if(dep[v]<dep[u]) E[id].dir=(u==E[id].u?1:-1);
	}
}
int main() {
	int n,m;read(n);read(m);
	for(int i=1,u,v;i<=m;i++)
	{
		read(u);read(v);
		G[u].push_back(mp(v,i));
		G[v].push_back(mp(u,i));
		E[i]=Edge(u,v);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
	for(int i=1;i<=m;i++) vis[i]=0;
	
	int rt=0;
	for(int i=1;i<=tot;i++) if(Siz[rt]<Siz[i]) rt=i;
	printf("%d\n",Siz[rt]);
	for(int i=1;i<=n;i++) vis[i]=0;
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]) dfs2(i,0);
	}
	for(int i=1;i<=tot;i++) G[i].clear();
	for(int i=1;i<=m;i++)
	{
		int u=E[i].u,v=E[i].v;
		if(belong[u]!=belong[v]) G[belong[u]].push_back(mp(belong[v],i)),G[belong[v]].push_back(mp(belong[u],i));
	}
	dfs(rt,0);
	for(int i=1;i<=m;i++)
	{
		if(E[i].dir==1) printf("%d %d\n",E[i].u,E[i].v);
		else printf("%d %d\n",E[i].v,E[i].u);
		assert(E[i].dir==1||E[i].dir==-1);
	}
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

Shik and Copying String

题意

\(~~~~\) 给出字符串 \(S_0\)\(T\)\(S_{i,j}\) 要么是 \(S_{i-1,j}\) 要么是 \(S_{i,j-1}\)。求使得 \(S_k=T\) 的最小的 \(k\).
\(~~~~\) \(1\leq |S_0|\leq 10^6\).

题解

\(~~~~\) 我们发现每个位置一定是由上个部分画一条折线。与前面的折线不交覆盖到当前位置所需的最小层数。

\(~~~~\) 那我们倒着来,找到每一段由原字符串的哪个字符作为起点,同时记录已有的折线是否干扰到当前折线,也就是记录前面的已有的折点,如果它到了当前的位置那就必须再保留。

\(~~~~\) 不过还是代码好理解一些。

代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int n,Q[1000005];
char S[1000005],T[1000005];
bool Equal(char A[],char B[])
{
	for(int i=1;i<=n;i++) if(A[i]!=B[i]) return false;
	return true;
}
int main() {
	read(n);
	scanf("%s %s",S+1,T+1);
	if(Equal(S,T)) return puts("0")&0;
	int pos=n,Head=1,Tail=0,Ans=0;
	for(int i=n;i>=1;i--)
	{
		if(T[i]==T[i-1]) continue;
		pos=min(pos,i);
		while(pos>=1&&S[pos]!=T[i]) pos--;
		if(!pos) return puts("-1")&0;
		while(Head<=Tail)
		{
			if(Q[Head]-(Tail-Head+1)+1>i) Head++;
			else break;
		}
		Q[++Tail]=pos;
		if(i!=pos) Ans=max(Ans,Tail-Head+1);
	}
	printf("%d",Ans+1);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

Game of AI (easy version)

题意

\(~~~~\) \(n\) 个 bot.初始时第 \(i\) 个 bot 占领了第 \(i\) 座塔。接下来会有一个排列 \(p\) 和一个序列 \(a\)。bot 将按照 \(p\) 中的顺序行动,若当前行动的 bot 有塔,则会攻击 \(a_{p_i}\) 这座塔,赶走那里的bot。最后 \(b\) 序列记录了每座塔被哪个bot占领。求可能的 \((a,b)\) 的数量。
\(~~~~\) \(1\leq n\leq 5000\).

题解

\(~~~~\) 我们发现若最终 \(b_i \not = i\) ,那就说明 \(b_i\) 的目标必然是 \(i\),也就是 \(a_{b_i}=i\)。否则 \(b_i=i\) ,此时对于所有 \(a_j=i\)\(j\) 应该有 \(b_j \not = j\),这样才能使得 \(j\) 先被占领,可以安排合适的顺序。

\(~~~~\) 那么对于 \(b_i \not = i\) 的那些位置,我们连接一条 \(i\)\(b_i\) 的边,那我们就可以找到很多有趣的东西。比如说最后的图一定是若干条链,因为成环了的话那就不可能有解了。链上有入边的点的 \(a_i\) 已经确定了,只需要考虑链头的 \(a_i\) 分配。而对于链我们分成长度为 \(2\) (结点数为 \(2\)) 和非 \(2\) ,前一类我们的链头的 \(a_i\) 只能指向非链尾的位置,这样才能安排顺序被取代这个bot占领的塔。而后一类的链头我们就可以随便把 \(a_i\) 安排成非自身的,因为它可以被任意安排顺序取代。

\(~~~~\) 用组合数算一算就好了。

代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int MOD,Fac[5005],Inv[5005],Pow[5005][5005];
inline int Add(int a,int b){a+=b;return a>=MOD?a-MOD:a;}
inline int Dec(int a,int b){a-=b;return a<0?a:a+MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a);
	}
	return ret;
}
inline int C(int n,int r){return n<r?0:Mul(Fac[n],Mul(Inv[r],Inv[n-r]));}
int main() {
	int n;read(n);read(MOD);
	Fac[0]=1;
	for(int i=1;i<=5000;i++) Fac[i]=Mul(Fac[i-1],i);
	Inv[5000]=qpow(Fac[5000],MOD-2);
	for(int i=4999;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
	for(int i=1;i<=5000;i++)
	{
		Pow[i][0]=1;
		for(int j=1;j<=5000;j++) Pow[i][j]=Mul(Pow[i][j-1],i);
	}
	int Ans=0;
	for(int i=1;i*2<=n;i++)
		for(int j=0;j+i*2<=n;j++)
			Ans=Add(Ans,Mul(C(n,j),Mul(Fac[n-j],Mul(Mul(Pow[n-1][i],Pow[n-i-j][j]),Mul(C(n-i-j-1,i-1),Inv[i])))));
	printf("%d",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

鲜花

\(~~~~\) 今天感觉是很摆的一天,但我希望不是可以摆的最后一天/kk

\(~~~~\) 上午做了一道胡了两道没写,下午也没写代码然后 4:10 搬到下面机房过后直接开摆指导sha老师做题。

\(~~~~\) 晚上想了上面写的最后这道题easy version,hard version弃了然后就来写日寄了。

\(~~~~\) 总结:模拟赛是第一生产力。

posted @ 2023-03-02 22:38  Azazеl  阅读(45)  评论(0编辑  收藏  举报