20250609测试总结

T1 P4063 [JXOI2017] 数列

题目

Description

九条可怜手上有一个长度为 \(n\) 的整数数列 \(r_i\),她现在想要构造一个长度为 \(n\) 的,满足如下条件的整数数列 \(A\)

  • \(1 \le A_i \le r_i\)

  • 对于任意 \(3 \le i \le n\) ,令 \(R\)\(A_1\)\(A_{i-2}\) 中大于等于 \(A_{i-1}\) 的最小值,\(L\)\(A_1\)\(A_{i-2}\) 中小于等于 \(A_{i-1}\) 的最大值。\(A_i\) 必须满足 \(L \le A_i \le R\) 。如果不存在大于等于 \(A_{i-1}\) 的,那么 \(R=+\infty\) ;如果不存在小于等于 $A_{i-1} $ 的,那么 \(L = -\infty\)

现在可怜想要知道共有多少不同的数列满足这个条件。两个数列 \(A\)\(B\) 是不同的当且仅当至少存在一个位置 \(i\) 满足 \(A_i \neq B_i\)

\(n\le50\)\(r_i\le150\)

Solution

赛时写了一个很显然的 dp 。

从前往后填,前边的有效限制信息为:上一个是什么,下界,上界。

因此设 \(dp[i][l][k][r]\) 表示填数范围 \(l\sim r\) 且上一个填的是 \(k\)

转移:

  1. \(l\)\(dp[i+1][l][l][l] \gets dp[i][l][k][r]\)

  2. \(l+1\sim k-1\)\(dp[i+1][l][k'][k] \gets dp[i][l][k][r]\)

  3. \(k\)\(dp[i+1][k][k][k] \gets dp[i][l][k][r]\)

  4. \(k+1\sim r-1\)\(dp[i+1][k][k'][r] \gets dp[i][l][k][r]\)

  5. \(r\)\(dp[i+1][r][r][r] \gets dp[i][l][k][r]\)

注意第 \(1\) 条和第 \(5\) 条转移分别要求 \(l≠k\)\(r≠k\)(否则就和第 \(3\) 条重复了)。

#include<bits/stdc++.h>
#define F(i,j,k) for(int i=j;i<=k;i++)
#define N 51
#define L 152
#define mod 998244353
using namespace std;
int dp[N][L][L][L],n,m[N],ans;
inline int mo(int x){return x<mod?x:x-mod;}
inline void add(int &x,int y){x=mo(x+y);}
inline void tran(int i,int l,int k,int r){
	if(l!=k)add(dp[i+1][l][l][l],dp[i][l][k][r]);
	F(j,l+1,k-1)add(dp[i+1][l][j][k],dp[i][l][k][r]);
	add(dp[i+1][k][k][k],dp[i][l][k][r]);
	F(j,k+1,r-1)add(dp[i+1][k][j][r],dp[i][l][k][r]);
	if(k!=r)add(dp[i+1][r][r][r],dp[i][l][k][r]);
}
int main(){
	cin>>n;
	F(i,1,n)cin>>m[i];
	F(i,1,m[1])dp[1][0][i][151]=1,tran(1,0,i,151);
	F(i,2,n-1)F(k,1,m[i])F(l,0,k)F(r,k,151)if(dp[i][l][k][r])tran(i,l,k,r);
	F(k,1,m[n])F(l,0,k)F(r,k,151)if(dp[n][l][k][r])add(ans,dp[n][l][k][r]);
	cout<<ans;
}

时间复杂度 \(O(n\times r^4)\) 不过有剪枝跑不满。

(由于是 \(IOI\) 赛制,赛时过了,就没再想)

然而赛后打开题解发现正解时间复杂度 \(O(n\times r^3)\)

选取比较有代表性的做法:

倒着填,可以发现当钦定后两个数的时候,再往前的位置只有一个区间不允许填的限制,于是把三位状态变成了两维。转移也比较正常,可能有几处细节。

T2 P5290 [十二省联考 2019] 春节十二响

题目

Description

有一颗有根树,点有点权 \(a_i\) 。你需要把所有点分为若干类,要求每一类中的点不存在祖先关系。每一类的代价为其中的点的点权最大值。最终的代价为每一类代价之和。

要求最小化最终的代价并输出。

\(1\le n\le 2\times 10^5, 1\le a_i\le 10^9\)

Solution

先考虑整棵树为链(但是根节点不一定是链的一端)怎么做。

可以想到贪心:先制造链的较长的一端的“类”,对于另一端,新建类一定不优,那么向建好的类合并。想到贪心:分别排序,然后按照从大到小的顺序取 \(max\)

于是对于普通情况一样操作,启发式合并即可。

时间复杂度 \(O(n\log n)\),复杂度证明类似于线段树合并。(注意不是 \(O(n\log^2 n)\)

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define pb push_back
using namespace std;
int n,m[N],f[N],dep[N],mxdp[N],son[N],res[N];
ll ans;
vector<int>t[N];
priority_queue<int>q[N];
inline void dfs(int u){
	dep[u]=dep[f[u]]+1,mxdp[u]=dep[u];
	for(int v:t[u]){
		dfs(v),mxdp[u]=max(mxdp[u],mxdp[v]);
		if(mxdp[v]>mxdp[son[u]])son[u]=v;
	}
}
inline void dfs2(int u){
	if(son[u])dfs2(son[u]),swap(q[son[u]],q[u]);
	for(int v:t[u])if(v!=son[u]){
		dfs2(v);
		int tmp=q[v].size();
		while(q[v].size()){
			res[q[v].size()]=max(q[v].top(),q[u].top());
			q[v].pop(),q[u].pop();
		}
		for(int i=1;i<=tmp;i++)q[u].push(res[i]);
	}
	q[u].push(m[u]);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>m[i];
	for(int i=2;i<=n;i++)cin>>f[i],t[f[i]].pb(i);
	dfs(1),dfs2(1);
	while(q[1].size())ans+=q[1].top(),q[1].pop();
	cout<<ans;
}

T3 P5292 [HNOI2019] 校园旅行

题目

Description

给定一个 \(n\) 个点 \(m\) 条边的图,点有 \(0/1\) 点权。\(q\) 次询问,给出 \(u,v\)$ 回答能否找到一条路径(不要求简单路径)使得经过的点的权值形成回文串。

\(1 \leq n \leq 5000\)\(1 \leq m \leq 5\times 10 ^ 5\)\(1 \leq q \leq 10 ^ 5\)

Solution

先考虑暴力,从合法点对开始记忆化搜索,每个点对最多进行一次操作,每次操作最坏情况下两个点分别枚举出边 O(m) 次,然而总边数为 \(m\) ,因此均摊时间复杂度 \(O(m^2)\)

#include<bits/stdc++.h>
#define pb push_back
#define N 5005
#define M 500005
using namespace std;
int n,m,q,tag[N];
bool a[N],ans[N][N];
struct edge{int u,v;}e[M];
char ch;
vector<int>t0[N],t1[N];
inline void dfs(int u,int v){
	if(u>v)swap(u,v);
	if(ans[u][v])return;
	ans[u][v]=1;
	for(int v1:t0[u])for(int v2:t0[v])dfs(v1,v2);
	for(int v1:t1[u])for(int v2:t1[v])dfs(v1,v2);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)cin>>ch,a[i]=ch=='1';
	for(int i=1;i<=m;i++){
		cin>>e[i].u>>e[i].v;
		if(a[e[i].v])t1[e[i].u].pb(e[i].v);
		else t0[e[i].u].pb(e[i].v);
		if(a[e[i].u])t1[e[i].v].pb(e[i].u);
		else t0[e[i].v].pb(e[i].u);
	}
	for(int i=1;i<=n;i++)dfs(i,i);
	for(int i=1;i<=m;i++)if(a[e[i].u]==a[e[i].v])dfs(e[i].u,e[i].v);
	for(int i=1,u,v;i<=q;i++){
		cin>>u>>v;
		cout<<(ans[min(u,v)][max(u,v)]?"YES\n":"NO\n");
	}
}

考虑优化。注意到点数比较小,尝试去掉一些无用边。

对于一个回文串,左右对称的部位,可能是 01交替 或 只走0 或 只走1。我们单独考虑每一段。可以发现一段的奇偶性固定但长度不固定:要么只能为偶,要么只能为奇,要么随便。这取决于什么?取决于这种边是否构成二分图。如果不能构成,则随便。

进而想到,对于一个二分图,有用的是其任意一棵生成树。不是二分图呢?为了仍然能够满足随便到达,给它的任意一颗生成树加一条重边即可。

然后边数变为 \(O(n)\) 量级,跑上边的暴力即可。

#include<bits/stdc++.h>
#define pb push_back
#define N 5005
#define M 500005
using namespace std;
int n,m,q,fm[3][N];
bool a[N],vis[3][N],vis2[3][N],tag[3][N],ans[N][N];
char ch;
vector<int>t[3][N],g[N];
inline bool dfs(int k,int u,bool tg){
	if(vis[k][u])return tag[k][u]^tg;
	vis[k][u]=1,tag[k][u]=tg;
	bool sf=0;
	for(int v:t[k][u])sf|=dfs(k,v,tg^1);
	return sf;
}
inline void dfs2(int k,int u){
	vis[k][u]=0;
	for(int v:t[k][u])if(vis[k][v])g[u].pb(v),g[v].pb(u),dfs2(k,v);
}
inline void dfs3(int u,int v){
	if(u>v)swap(u,v);
	if(ans[u][v])return;
	ans[u][v]=1;
	for(int v1:g[u])for(int v2:g[v])if(a[v1]==a[v2])dfs3(v1,v2);
	for(int v1:g[u])for(int v2:g[v])if(a[v1]==a[v2])dfs3(v1,v2);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)cin>>ch,a[i]=ch=='1';
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		if(a[u]&&a[v])t[0][u].pb(v),t[0][v].pb(u);
		if(!a[u]&&!a[v])t[1][u].pb(v),t[1][v].pb(u);
		if(a[u]^a[v])t[2][u].pb(v),t[2][v].pb(u);
	}
	for(int k=0;k<3;k++)for(int i=1;i<=n;i++)if(!vis[k][i])fm[k][i]=dfs(k,i,0)+1;
	for(int k=0;k<3;k++)for(int i=1;i<=n;i++)if(fm[i])dfs2(k,i),fm[k][i]==2?g[i].pb(i),0:0;
	for(int i=1;i<=n;i++)dfs3(i,i);
	for(int u=1;u<=n;u++)for(int v:g[u])if(a[u]==a[v])dfs3(u,v);
	for(int i=1,u,v;i<=q;i++)cin>>u>>v,cout<<(ans[min(u,v)][max(u,v)]?"YES\n":"NO\n");
}

总结:赛时 \(100+60+30\) 但是 \(T2\) 确实应该切掉。

其实,我一开始做链的时候随便写了一个用贪心合并数组(其实是对的),但是一个细节写错了,导致链的15pt就得了5pt,然后我就去看t3了。最后还剩10分钟的时候我发现问题了,改了一下,链的部分分过了然后就知道正常的树和链一样启发式合并就好,但是写不完了。

posted @ 2025-06-09 15:55  linjingxiang  阅读(21)  评论(0)    收藏  举报