首页 > 其他分享 >[47] (CSP 集训) CSP-S 模拟 11

[47] (CSP 集训) CSP-S 模拟 11

时间:2024-10-14 19:33:51浏览次数:1  
标签:11 val int 47 tor mp 500001 now CSP

因为有人想看 T3 \(nk^2\) 所以先发一下

A.玩水

注意到只有在形如下面这样的地方才会发生分叉

?a
a?

所以 \(f_{i,j}\) 表示从 \((1,1)\) 到 \((i,j)\) 的矩阵中的最大路径方案数,考虑转移

普通的转移是 \(f_{i,j}=\max(f_{i,j-1},f_{i-1,j})\)

如果 \(a_{i-1,j}=a_{i,j-1}\),则有 \(f_{i,j}=2f_{i-1,j-1}\)

因为 \((i-1,j-1)\) 处的方案数都可以通过向下或者向右到达 \((i,j)\),所以乘二

这个东西是假的,但是只会在下面这两种时候假

ab
bc
cd

abc
bcd

也就是两个 \(a_{i-1,j}=a_{i,j-1}\) 是相邻的,这个时候 DP 就会出错

但是你可以发现,一旦有相邻的 \(a_{i-1,j}=a_{i,j-1}\),那么方案数就一定大于等于 \(3\) 了,此时直接返回即可

因此先把这种情况判掉,再做 DP 就行了

#include<bits/stdc++.h>
using namespace std;
char mp[1001][1001];
int f[1001][1001];
int n,m;
bool check(){
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(mp[i][j-1]==mp[i-1][j]){
                if((i>2 and j>1 and mp[i-1][j-1]==mp[i-2][j]) or (i>1 and j>2 and mp[i][j-2]==mp[i-1][j-1])){
                    return true;
                }
            }
        }
    }
    memset(f,0,sizeof f);
    f[1][1]=1;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(i==1 and j==1) continue;
            if(i!=1) f[i][j]=max(f[i][j],f[i-1][j]);
            if(j!=1) f[i][j]=max(f[i][j],f[i][j-1]);
            if(i!=1 and j!=1 and mp[i][j-1]==mp[i-1][j]){
                f[i][j]=max(f[i][j],f[i-1][j-1]*2);
            }
        }
    }
    return f[n][m]>=3;
}
int t;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;++i) scanf("%s",mp[i]+1);
		if(check()) printf("1\n");
		else printf("0\n");
	}
}

B.AVL树

先序遍历字典序优先 = 中序遍历字典序优先

所以按照先序便利顺序考虑每个点,如果能够加入就加入,不能加入则跳过该子树

首先有一个非常好的递推性质:一颗深度为 \(i\) 的 AVL 最少的节点数 \(fi=fi−1+fi−2+1\)

然后就是贪心,顺次考虑每个点时,我们每次向上跳,如果当前点作为左子树,算出右子树至少需要加入多少点,如果加入当前点需要加入的点数小于等于剩余点数就加入

需要维护当前节点的最大子树深度,子树深度的合法下界,这都是可以通过一遍 dfs 确定的

#include<bits/stdc++.h>
using namespace std;
int n,k;
int res,root;
int tol[500001],tor[500001];
int fa[500001];
int maxn[500001],minn[500001];
int deep[500001],ndeep[500001];
bool flag[500001];
int f[500001];
void prework(int now){
    maxn[now]=deep[now]=deep[fa[now]]+1;
    if(tol[now]){
        prework(tol[now]);
        maxn[now]=max(maxn[now],maxn[tol[now]]);
    }
    if(tor[now]){
        prework(tor[now]);
        maxn[now]=max(maxn[now],maxn[tor[now]]);
    }
}
int check(int now){
    int ndep=max(deep[now],ndeep[now]);
    int ans=1;
    while(fa[now]){
        bool left=(tol[fa[now]]==now);
        now=fa[now];
        ndep=max(ndep,ndeep[now]);
        if(left and tor[now]){
            ans+=f[max(ndep-1,minn[tor[now]])-deep[now]];
        }
    }
    return ans;
}
void update(int now){
    int ndep=ndeep[now]=max(ndeep[now],deep[now]);
    while(now){
        now=fa[now];
        ndeep[now]=ndep=max(ndeep[now],ndep);
        if(tor[now]){
            minn[tor[now]]=max(minn[tor[now]],ndep-1);
        }
    }
}
void dfs(int now){
    int cnt=check(now);
    if(cnt<=res){
        update(now);
        flag[now]=true;
        res--;
        if(tol[now] and tor[now]){
            int dt=maxn[tol[now]]<minn[now];
            minn[tol[now]]=max(minn[tol[now]],minn[now]-dt);
            minn[tor[now]]=max(minn[tor[now]],minn[now]+dt-1);
            dfs(tol[now]);
            dfs(tor[now]);
            return;
        }
        if(tol[now]){
            minn[tol[now]]=max(minn[tol[now]],minn[now]);
            dfs(tol[now]);
        }
        if(tor[now]){
            minn[tor[now]]=max(minn[tor[now]],minn[now]);
            dfs(tor[now]);
        }
    }
}
int main(){
    freopen("avl.in","r",stdin);
    freopen("avl.out","w",stdout);
    cin>>n>>k;res=k;
    for(int i=1;i<=n;++i){
        int x;cin>>x;
        if(x==-1) root=i;
        else{
            fa[i]=x;
            if(i>x) tor[x]=i;
            else tol[x]=i;
        }
    }
    f[1]=1;f[2]=2;
    for(int i=3;i<=n;++i){
        f[i]=(f[i-1]+f[i-2]+1);
    }
    // cout<<"?";
    prework(root);
    // cout<<"??";
    dfs(root);
    // cout<<"???";
    for(int i=1;i<=n;++i){
        cout<<flag[i];
    }
}

C.暴雨

设 \(f_{i,j,k,0/1}\) 表示考虑前 \(i\) 个,其中最大值高度为 \(j\)(这一维没法开,记录排名),后面也存在一个高度不小于 \(j\) 的土块(即 \(j\) 高度能造成贡献(最终水位高度为 \(j\),这样就能直接转移的时候计算每个格子的终态贡献了)),并且前 \(i\) 个中推平了 \(k\) 个,当前积水的体积是奇数/偶数的总方案数

你会发现这么个东西没办法开,但是我们第二维可以只开 \(k\) 个,因为只有最高的 \(K(+1)\) 个值会对答案有贡献,即使你把所有最高的 \(K\) 个全推平了,也不会轮到 \(K(+1)\) 以外的当最高值

分别前后算,枚举中转点统计答案,要枚举中转点是因为我们在内层设计 DP 数组的时候钦定一定在后面有一个不小于排名 j 的值的元素,所以这里枚举中转点的时候对接一下

有几个辅助数组不开做不了,主要是排名和值的转换有关的

懒得喷,注释啥的都在代码里,写的挺细的


牛魔,怎么还有看不懂注释的

假设你一开始什么数都不填,先钦定一个终态最小值 \(j\)(这就要求你填的数不能超过 \(j\)),那么,如果你现在填入一个高度为 \(h\) 的数字,有以下两种情况

  • \(h\le j\),由于终态已经确定,此时你可以直接算出在这一位产生的贡献,即 \(j-h\)
  • \(h\ge t\),此时已经不满足终态是 \(j\) 的限制,你可以通过拉高 \(j\) 来解决这个问题(也就是说,这个情况要求你从 \(f_{i,j,k,l}\) 转移到一个 \(f_{i+1,h,k',l'}\))

但是这些贡献能被算出是有条件的,那就是必须有一个大于等于 \(j\) 的值来兜底,否则你存的这些水就流出边界了,这个条件后面会用到

然后就是怎么判断状态能被统计到答案里

首先我们对序列正着倒着分别跑一遍,统计出答案,可以想到,我们应该枚举一个中转点(这个中转点是极大的,它可以将整个序列分成互不相关的左右两部分),然后将左右两边的方案数相乘

现在你枚举这个最高的兜底的值,然后判断它是否能作为两边的 “兜底” 的值(也就是它是否大于等于两边的 \(j\)),如果可以,那么就统计进答案里

理解了这个思路,推平操作就很简单了,当你从 \(i\) 转移到 \(i+1\) 的时候,考虑是否推平 \(i+1\),如果 \(a_{i+1}\) 更大,推平可以导致 \(j\) 不发生变化,否则只会导致对高度的贡献由 \(j-h\) 变成 \(j-0\)

#include<bits/stdc++.h>
using namespace std;
const int p=1e9+7;
int n,k,ans;
int h[25001],id[25001];
struct DP{
    //记录需要 DP 的数组
    //因为会有倒着做的所以就加了一个这个
    int a[25001];

    //考虑前 i 个,其中最大值高度为 j(这一维没法开,记录排名),后面也存在一个高度不小于 j 的土块
    //(即 j 高度能造成贡献)
    //并且前 i 个中推平了 k 个,当前积水的体积是奇数/偶数
    //的总方案数
    int f[25001][26][26][2];
    //排名只记录 K 个是因为,只有最高的 K(+1) 个值会对答案有贡献,即使你把所有最高的 K 个全推平了
    //也不会轮到 K(+1) 以外的当最高值

    //记录前 i 个数中第 j 大的值
    int val[25001][26];

    int cnt[25001];

    //set 用于维护 val 数组
    set<int> s;

    //查某个 val 在 i 中对应的排名
    map<int,int> mp[25001];

    inline void operator()(){
        s.insert(0);
        for(int i=1;i<=n;++i){
            s.insert(a[i]);
            auto iter=s.end();
            for(int j=1;j<=k+1;j++){
                if(iter==s.begin()) break;
                iter--;
                mp[i][*iter]=++cnt[i];
                val[i][cnt[i]]=*iter;
            }
        }
        f[0][1][0][0]=1;
        cnt[0]=1;
        val[0][1]=0;
        for(int i=0;i<n;i++){  //从 i 向 i+1 转移
            for(int j=1;j<=cnt[i];j++){
                for(int l=0;l<=k;l++){
                    for(int u=0;u<=1;u++){
                        if(f[i][j][l][u]){
                            if(val[i][j]>=a[i+1]){   //新增的柱子更低,最高值不变
                                //这里形如 mp[i+1][val[i][j]] 的写法,实际上是求 “在前 i 个数中排名为 j 的数在前 i+1 个数中的排名”,也就是最值没变(只是排名可能变了)
                                f[i+1][mp[i+1][val[i][j]]][l][(u+val[i][j]-a[i+1])%2]=(f[i+1][mp[i+1][val[i][j]]][l][(u+val[i][j]-a[i+1])%2]+f[i][j][l][u])%p;  //新增的数做出两者高度差的贡献(因为状态设计的时候说一定有一个)
                                                                                                                                                                //后面的柱子大于等于排名 j 的值,所以这里直接加上 [a[i+1],val[i][j]] 之间的所有贡献
                                if(l+1<=k) f[i+1][mp[i+1][val[i][j]]][l+1][(u+val[i][j])%2]=(f[i+1][mp[i+1][val[i][j]]][l+1][(u+val[i][j])%2]+f[i][j][l][u])%p;  //推平 i+1,加上 [0,val[i][j]] 之间的贡献
                            }
                            else{                   //新增的柱子更高
                                f[i+1][mp[i+1][a[i+1]]][l][u]=(f[i+1][mp[i+1][a[i+1]]][l][u]+f[i][j][l][u])%p;                                          //最高的数是 a[i+1],新增的数不作贡献(a[i+1]-a[i+1]=0)
                                                                                                                                                        //这里更改了最高的值,不再从原数组转移贡献
                                                                                                                                                        //相当于你从中间截断了,之前的贡献(被截开的前半段)计算了就不会变了,下面你对这个最高值的转移计算的是后半段的贡献
                                if(l+1<=k) f[i+1][mp[i+1][val[i][j]]][l+1][(u+val[i][j])%2]=(f[i+1][mp[i+1][val[i][j]]][l+1][(u+val[i][j])%2]+f[i][j][l][u])%p;   //推平 i+1,最高值不变,直接造成整个贡献
                            }
                        }
                    }
                }
            }
        }
    }
};
//分别前后算,枚举中转点统计答案
DP dpforward,dpbackward;

int main(){
    ios::sync_with_stdio(false);
    freopen("rain.in","r",stdin);
    freopen("rain.out","w",stdout);
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>h[i];
        dpforward.a[i]=dpbackward.a[n-i+1]=h[i];
        id[i]=i;
    }
    dpforward();dpbackward();
    sort(id+1,id+n+1,[](int x,int y){return h[x]>h[y];});
    //因为我们在内层设计 DP 数组的时候钦定一定在后面有一个不小于排名 j 的值的元素,所以这里枚举中转点的时候对接一下
    for(int i=1;i<=n;i++){
        if(h[id[i]]<h[id[k+1]]) break;   //只枚举前 K+1 个元素,当然你这里写成 i<=K+1 会错,因为可能有不止一个元素值等于 K+1 的
        for(int j=dpforward.cnt[id[i]-1];j;--j){
            if(dpforward.val[id[i]-1][j]>h[id[i]]) break;
            for(int l=dpbackward.cnt[n-id[i]];l;--l){
                if(dpbackward.val[n-id[i]][l]>=h[id[i]]) break;
                for(int u=0;u<=k;u++){
                    ans=(ans+1ll*dpforward.f[id[i]-1][j][u][0]*dpbackward.f[n-id[i]][l][k-u][0]%p)%p;
                    ans=(ans+1ll*dpforward.f[id[i]-1][j][u][1]*dpbackward.f[n-id[i]][l][k-u][1]%p)%p;
                }
            }
        }
    }
    cout<<ans%p;
}

后面的仙姑


私は雨 / 稻叶昙

私は誰 あなたの哀れ
夜空の中で 名前を無くして
うねりのない 水面に潜む景色を
知らないまま (霧になってしまっても)
漂う雲 (別にいいのに)
昨日までは (構わないのに) 漂う雲
私はなぜ 真っすぐに落ちる
だれかの手のひらを探すため
空をできる限り 目に収めながら
私は雨 弾かれて判る
だれかのようにはなれない雨
地球を困らせるほどの痛みを知らないから
私は雨 セカイを暈す
夜明けに導かれている雨
流れ着いた海の隠し味を知るまで
星を隠した雷鳴と
視界からはみ出した 積乱雲
できるだけ できるだけ できるだけ
離れていたかった
傘をさす 余裕はないし
このままでもいいと思えるよ
わからないから 染み込んでるの
夜の強い雨で 目を覚ます
私は雨 地球をなぞる
一粒では気付くことのない雨
夜空に飾り付ける 星を見つけて
空に浮かんだり 地に足をつけたり
消えかかったり 溢れかえったりする
描けていたら 何も起きなかった
セカイ的気候変動
私は雨 滴って判る
だれかのようにはなれない雨
地球を困らせるほどの思いを知りたいから
私は雨 セカイを暈す
夜明けに導かれている雨
流れ着いた海の隠し味になるまで
私は雨
辿り着くまでに
おさらいを忘れないで
凪の海で向かい合わせ
違う景色 同じ模様の 答え合わせ

     />  フ
     |  _  _|
     /`ミ x 彡
     /      |
    /  ヽ   ?
 / ̄|   | | |
 | ( ̄ヽ_ヽ))
 \二つ

标签:11,val,int,47,tor,mp,500001,now,CSP
From: https://www.cnblogs.com/HaneDaCafe/p/18464608

相关文章

  • 【2024潇湘夜雨】WIN10_Ent-G_22H2.19045.5011软件选装纯净特别版10.14
    【系统简介】=============================================================1.本次更新母盘来自WIN10_Ent-G_22H2.19045.5011.进桌面后稍等片刻,等待后续部分优化完成。2.全程离线精简、无人值守调用优化处理制作。部分优化适配系统可能要重启几次,即使显示适配失败也不要在意,可能......
  • 题解:P11132 【MX-X5-T4】「GFOI Round 1」epitaxy
    ProblemLink【MX-X5-T4】「GFOIRound1」epitaxy题目描述给你两个正整数\(n,m\)。定义一个\(1\simn\)的排列\(p\)的价值为所有的\(n-m+1\)个长度为\(m\)的连续子串内最大值的最大公因数。(规定单个数的最大公因数为其自身。)请你求出一个在所有\(1\simn\)......
  • P4774 [NOI2018] 屠龙勇士
    典题。题目不难,注意细节就行。#include<iostream>#include<iomanip>#include<cstdio>#include<vector>#include<stack>#include<queue>#include<bitset>#include<map>#include<set>#include<unordered_map&......
  • 题解:P11145 Strange Homura Game
    ProblemLinkStrangeHomuraGame题意让你猜测一个数\(n\),你只能输出两次,每次输出一个数\(x\),返回\(x\bmodn\)。Solution令输入的数为\(A,B\),输出的数为\(a,b\),答案为\(n\)。一开始想的是CRT,但只能询问\(2\)次。发现输入的值是经过\(\bmodn\)的,已知\((A-a)......
  • 题解:P11063 【MX-X4-T3】「Jason-1」数对变换
    ProblemLink【MX-X4-T3】「Jason-1」数对变换题外话场上把贪心注重在一个奇怪地方了,导致交的时候已经有\(9\)个人\(\textcolor{green}{AC}\)了(哭)。题意简述对于数对\((x,y)\),你可以执行以下两种变换:类型1:取\(1\lek\lex\),令\((x,y)\gets(\lfloor\frac{x}{k}......
  • csp-s模拟11
    csp-s模拟11\(T1\)T2203.玩水(water)\(100pts\)定义一个点\((i,j)\)是分点当且仅当\(s_{i,j+1}=s_{i+1,j}\),而一个点\((i,j)\)是合点当且仅当\((i-1,j-1)\)是分点。先考虑若只有两个人时,只要存在一个分点就一定有解。扩展到三个人时,若存在一个合点可以通过......
  • CSP模拟赛 #37
    A题意:给定\(n,a_{1\simn},b_{1\simn}\),两个点\(i,j\)之间有连边当且仅当\(a_i-a_j\lei-j\leb_i-b_j\)或\(a_j-a_i\lej-i\leb_j-b_i\),求图中连通块数量。\(1\len\le10^6\)考虑条件\(a_i-a_j\lei-j\leb_i-b_j\)相当于\(a_i-i\lea_j......
  • csp-s模拟11
    E题面最暴力的做法,枚举连续段长度\(i\),然后暴力搜索,复杂度\(O(n^3)\)点击查看代码#include<bits/stdc++.h>#definespeed()ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);#definelllonglong#definepbpush_back#defineullunsignedlonglong#define......
  • Windows11下安装wsl报错:无法解析服务器的名称或地址
    问题描述之前在自己的笔记本电脑(Windows10)上下载安装WSL很顺利,具体教程见前面的文章,但是在新电脑(Windows11)上下载就报错:无法解析服务器的名称或地址,按照网上说的两个解决方案:修改 DNS 为手动114.114.114.114;查询 raw.githubusercontent.com 这个域名对应的能ping通的ip,......
  • Windows11右键菜单一取消/恢复Win11二级菜单
    Win11更新后,右键菜单很多功能使用时需要点击“显示更多选型”才能获取完整功能,以下有几种方法可以自动展开Win11的二级菜单,恢复到Win10模式。​方法一:reg1.按住win+R打开运行窗口​2.输入【regedit】进入注册表编辑器定位到以下位置:计算机\HKEY_CURRENT_USER\Software\Cl......