CSP-S开小灶6 玩水,AVL树,暴雨,置换

T1:简单模拟;T2:树上前序遍历贪心;T3:DP;T4:咕了

T1:n*m的方格,每个格子上有不同字母,要求从(1,1)出发,只能走下或者右,到达(n,m),问存不存在至少3种不重复路径,路径经过的字母连起来相同。(n,m<=5e3)

考场思路:dp[i][j]代表到(i,j)的路径数量,\(dp[i][j]=max(dp[i]][j],dp[i-1][j-1]*2) 如果s[i-1][j]==s[i][j-1]\),否则\(=max(dp[i-1][j],dp[i][j-1])\)但是这样不对,因为会漏掉情况,
ab
bk
kg
这样合法,但是dp的时候(3,2)从(2,1)转移还是2,但是应该是3,因为本身s[i-1][j],s[i][j-1]就会存在>1的路径,不一定是从s[i-1][j-1]继承的。
正解:找到2个斜对角
a*
*b
可以相邻,可以一个上一个下,记录一下,然后nm枚举就行。
from Chen_jr

点击查看代码


#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
	return x;
}

const int maxn = 1005;
char mp[maxn][maxn];
bool f[maxn][maxn], p[maxn][maxn];
int n, m;
bool check(){
	for(int i = 1; i < n; ++i)
		for(int j = 1; j < m; ++j)
			p[i][j] = f[i][j] = (mp[i + 1][j] == mp[i][j + 1]);
	for(int i = n - 2; i > 0; --i)
	  for(int j = 1; j < m; ++j){
		  if(p[i][j] && p[i + 1][j])return true;
		  f[i][j] |= f[i + 1][j];
	  }
	for(int i = n - 1; i > 0; --i){
		for(int j = m - 2; j > 0; --j){
			if(p[i][j] && p[i][j + 1])return true;
			if(p[i][j] && f[i + 1][j + 1] && i + 1 < n && j + 1 < m)return true;
			f[i][j] |= f[i][j + 1];
		}
	}
	return false;
}
int main(){
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	int T = read();
	for(int i = 1; i <= T; ++i){
		n = read(), m = read();
		for(int i = 1; i <= n; ++i)scanf("%s", mp[i] + 1);
		if(check())printf("1\n");
		else printf("0\n");
	}
	return 0;
}

T2:给你一棵avl树,n个节点,要求保留K个点,如果x删除那么x子树一定删除,求最小中序遍历。(n<=5e5)

暴力:枚举保留的节点,\(O(2^n * n)\)
正解:
h[x]:代表已经加入的节点构成的树,x子树的最大深度;
mh[x]:依据已经遍历过的左子树和avl树本身的限制的选x的合法最小深度;
mdep[x]:所有的节点都算,x子树最大深度
dep[x]:深度;
中序遍历最优=前序遍历最优:\(BAC andABC\)最小子树和父节点是关联的。
按照前序最优,先尽量选左子树,然后再选右子树。
在递归到这个点,先检查如果选它最少需要保留多少节点。
就是利用h[x],往上跳子树,如果这是右儿子,那么左儿子的情况我已经知道了,不用管,如果是左儿子,依据左儿子,算出如果要使得x合法,最少右子树需要掏出多少节点\(cnt[max(y-1,mh[rs[fa[x]]])]\)要么被父亲管(父亲作为儿子和另一个儿子的限制),要么被左儿子管(左儿子深度限制),选一个最大。(最严格)。如果可以加入,那么就用当前dep去更新h[x],向上pushup,同样右儿子应当被已经更新的限制继续更新\(h[x]-1\),下限。而后面在x选的前提下,去更新左右儿子的限制,pushdown。
为什么mh[x]和h[x]分开:mh[x]是待选,你不知道选不选,但是选了就要满足要求。

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
/**/
const int N=5e5+100;
int cnt[45],h[N],mh[N],dep[N],mdep[N],ans[N],ls[N],rs[N],fa[N];
int n,K,root;
inline void get_dep(int x)
{
	dep[x]=mdep[x]=dep[fa[x]]+1;
	if(ls[x])get_dep(ls[x]),mdep[x]=max(mdep[x],mdep[ls[x]]);
	if(rs[x])get_dep(rs[x]),mdep[x]=max(mdep[x],mdep[rs[x]]);
}
//mdep[x]:子树节点最大深度
inline int calc(int x)
{
	int y=dep[x],num=0;
	while(x)
	{
		if(!ans[x])++num;
		y=max(y,h[x]);
		if(x<fa[x]&&rs[fa[x]])
		{
			num+=cnt[max(y-1,mh[rs[fa[x]]])-dep[fa[x]]];
		}
	
		x=fa[x];
	}
	//_f(i,1,n)
	//chu("calc:%d\n",num);
	//_f(i,1,n)chu("%d ",h[i]);
	//chu("\n");
	return num;
}
inline void insert(int x)
{
	h[x]=dep[x];int y=h[x];
	while(x)
	{
		if(!ans[x])ans[x]=1,--K;
		h[x]=max(h[x],y);
		if(x<fa[x]&&rs[fa[x]])
		{
			mh[rs[fa[x]]]=max(mh[rs[fa[x]]],h[x]-1);
		}
		x=fa[x];
	}
	return;
}
inline void solve(int x)
{
	if(calc(x)<=K)insert(x);
	else return;
	if(ls[x]&&rs[x])
	{
		if(mdep[ls[x]]<mh[x])
		{
			mh[ls[x]]=max(mh[ls[x]],mh[x]-1);
			mh[rs[x]]=max(mh[rs[x]],mh[x]);
		}
		else
		{
			mh[ls[x]]=max(mh[ls[x]],mh[x]);
			mh[rs[x]]=max(mh[rs[x]],mh[x]-1);
		}
		solve(ls[x]);solve(rs[x]);
	}
	else
	{
		if(ls[x])mh[ls[x]]=max(mh[ls[x]],mh[x]),solve(ls[x]);
		if(rs[x])mh[rs[x]]=max(mh[rs[x]],mh[x]),solve(rs[x]);
	}
}
int main()
{
  freopen("avl.in","r",stdin);
  freopen("avl.out","w",stdout);
  n=re(),K=re();
  cnt[1]=1,cnt[2]=2;
  _f(i,3,40)cnt[i]=cnt[i-1]+cnt[i-2]+1;
  _f(i,1,n)
  {
	  int pi=re();
	  if(pi==-1)root=i;
	  else
	  {
		  if(i>pi)rs[pi]=i;
		  else ls[pi]=i;
		  fa[i]=pi;
	  }
  }
  get_dep(root);
  //_f(i,1,n)chu("mxdep[%d]:%d\n",i,mdep[i]);
  solve(root);
  _f(i,1,n)chu("%d",ans[i]);
    return 0;
}
/*
3 1
2
-1
2

8 5
3
1
-1
5
7
5
3
7

20 10
2
3
5
3
13
8
6
10
8
5
12
10
-1
15
18
15
16
13
18
19
01111001010110100100
*/

T3:有一条小路分成n个连续等长块,每个块有自己高度h[x],如果左右存在更高的块这个块就会积水(字面意思,很好理解),问把K个小路变成h=0,使得积水总数是偶数的方案数%1e9+7(n<=2.5e4,hi<=1e9)

显然DP对吧。对于每个点来说,它的积水会受到左边右边的同时影响,这样DP就很不好定义,左扫右扫都很尴尬。所以我们加一个维度[j]:表示1~i的最大值是j,后面的最大值>=j的方案数虽然后面不知道但是很好合并,直接枚举最大值,前缀后缀DP都搞出来合并就行。
\(dp[i][j][k][0/1]:从1~i位置最大值是j,后面强制存在>=j,把k个路变h=0,当前积水奇数/偶数的方案数\)
重点是转移的时候最大值这些数怎么表示,因为最多每个i只有K+1个j,所以对于每个i我们只保存K个状态,那么转移就利用相对关系提前处理,避免log的时间复杂度对于h相同的,强制左边取等,右边不取就行。

点击查看代码






#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
/*
对于n<=20,可以直接枚举删除的子集,检验是否合法
对于K=1,K=2,因为K是可以枚举的,所以都行。
K就是删除的
*/
const int maxn=25000+3,maxk=27;const ll mod=1e9+7;
set<int>S;
set<int>::iterator it;
int n,m,a[maxn];
int nex1[maxn][maxk],nex2[maxn][maxk],new1[maxn],new2[maxn],dp1[maxn][maxk][maxk][2],dp2[maxn][maxk][maxk][2];
int b1[maxn][maxk],b2[maxn][maxk],cnt1[maxn],cnt2[maxn];
//前i个数,删除了j个,最大值是k的
inline int find1(int i,int x)
{
    return lower_bound(b1[i],b1[i]+1+cnt1[i],x)-b1[i];
}
inline int find2(int i,int x)
{
    return lower_bound(b2[i],b2[i]+1+cnt2[i],x)-b2[i];
}
inline void upd(int &x,ll y)
{
    x=((ll)x+(ll)y)%mod;
}
inline void deal()
{
    n=re(),m=re();
    _f(i,1,n)a[i]=re();
    _f(i,1,n)
    {
        S.insert(a[i]);
        if(S.size()>m+1)S.erase(S.begin());
        for(it=S.begin();it!=S.end();++it)b1[i][++cnt1[i]]=*it;
    }
    S.clear();
    f_(i,n,1)
    {
        S.insert(a[i]);
        if(S.size()>m+1)S.erase(S.begin());
        for(it=S.begin();it!=S.end();++it)b2[i][++cnt2[i]]=*it;
    }
    _f(i,0,n-1)
    _f(j,0,cnt1[i])
    nex1[i][j]=find1(i+1,b1[i][j]);
    _f(i,1,n+1)
    _f(j,0,cnt2[i])
    nex2[i][j]=find2(i-1,b2[i][j]);
    _f(i,1,n)new1[i]=find1(i,a[i]);
    _f(i,1,n)new2[i]=find2(i,a[i]);
    dp1[0][0][0][0]=1;
    _f(i,0,n-1)
    _f(j,0,m)
    _f(k,0,cnt1[i])
    {
        if(dp1[i][j][k][0])
        {
            //不删除
            if(a[i+1]<b1[i][k])//如果小,有积水,不更新最值
            upd(dp1[i+1][j][nex1[i][k]][(b1[i][k]-a[i+1])&1],dp1[i][j][k][0]);
            else 
            upd(dp1[i+1][j][new1[i+1]][0],dp1[i][j][k][0]);
            //删除
            if(j<m)upd(dp1[i+1][j+1][nex1[i][k]][b1[i][k]&1],dp1[i][j][k][0]);
        }
        if(dp1[i][j][k][1])
        {
            if(a[i+1]<b1[i][k])//如果小,有积水,不更新最值
            upd(dp1[i+1][j][nex1[i][k]][(b1[i][k]-a[i+1])&1^1],dp1[i][j][k][1]);
            else 
            upd(dp1[i+1][j][new1[i+1]][1],dp1[i][j][k][1]);
            //删除
            if(j<m)upd(dp1[i+1][j+1][nex1[i][k]][b1[i][k]&1^1],dp1[i][j][k][1]);
        }
    }
    dp2[n+1][0][0][0]=1;
    f_(i,n+1,2)
    _f(j,0,m)
    _f(k,0,cnt2[i])
    {
         if(dp2[i][j][k][0])
        {
            //不删除
            if(a[i-1]<b2[i][k])//如果小,有积水,不更新最值
            upd(dp2[i-1][j][nex2[i][k]][(b2[i][k]-a[i-1])&1],dp2[i][j][k][0]);
            else 
            upd(dp2[i-1][j][new2[i-1]][0],dp2[i][j][k][0]);
            //删除
            if(j<m)upd(dp2[i-1][j+1][nex2[i][k]][b2[i][k]&1],dp2[i][j][k][0]);
        }
        if(dp2[i][j][k][1])
        {
            //不删除
            if(a[i-1]<b2[i][k])//如果小,有积水,不更新最值
            upd(dp2[i-1][j][nex2[i][k]][(b2[i][k]-a[i-1])&1^1],dp2[i][j][k][1]);
            else 
            upd(dp2[i-1][j][new2[i-1]][1],dp2[i][j][k][1]);
            //删除
            if(j<m)upd(dp2[i-1][j+1][nex2[i][k]][b2[i][k]&1^1],dp2[i][j][k][1]);
        }
    }
    int ans=0;
    _f(i,1,n)
    _f(j,0,m)//前面铲平多少个
    {
        for(rint k1=0;k1<=cnt1[i-1]&&b1[i-1][k1]<a[i];++k1)
        {
            if(dp1[i-1][j][k1][0])
            {
                for(rint k2=0;k2<=cnt2[i+1]&&b2[i+1][k2]<=a[i];++k2)
                {
                    upd(ans,1ll*dp1[i-1][j][k1][0]*dp2[i+1][m-j][k2][0]);
                }
            }
            if(dp1[i-1][j][k1][1])
            {
                for(rint k2=0;k2<=cnt2[i+1]&&b2[i+1][k2]<=a[i];++k2)
                {
                    upd(ans,1ll*dp1[i-1][j][k1][1]*dp2[i+1][m-j][k2][1]);
                }
            }
        }
    }
    chu("%d",ans);
}
signed main()
{
freopen("rain.in","r",stdin);
freopen("rain.out","w",stdout);
deal();
return 0;
}
/*
7 1
2 5 2 4 1 6 2
*/
posted on 2022-09-19 21:17  HZOI-曹蓉  阅读(72)  评论(0编辑  收藏  举报