2022NOIPA层联测5 挑战 天堂 药丸 按钮

T1【贪心+简单DP】给你2n的矩阵,代表有石头,_代表没有,你可以在1时间内移动1个石头,2个石头在一个格子里相当于合并了会一起移动。问最少花费把石头移动到一个格子里。(n<=1e5)

考场:

分析发现本质是把有石头的若干格子划分到一个连续段的跨度数,所以最终的位置无所谓。
考虑是否有一种构造方案使得决策最优,尝试了把所有石头都往上或者往下移动到一个地方,但是很容易有反例,fail...

正解:

问题在于你可以确定移动的时候尽量往同一个地方移动是更优的,但是不知道往哪里动,考虑dp
\(dp[i][j]:表示把1--j列的石头都移动到i行的最小花费\),转移不需要考虑j-1列,只讨论j列的石头,也不需要关注是不是需要有2行石头一块走的情况,因为这样一定不如在维护另一行移动的时候,把这行的合并上去更优(如果新加入一个,+1/+lenth,显然合并上去不劣),但是有可能从上面跳到下面。

点击查看代码
//慎独,深思,毋躁,自律,专注
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#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 inf 2147483647
#define ll long long 
#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=2e5+100;
char s[2][N];
int T,n;
int dp[2][N];
inline int Dev(int x,int y)
{
    int dev=x-y;
    if(dev>0)return dev;
    return 0;
}
inline void Deal()
{
  //  memset(dp,0,sizeof(dp));
    n=re();
    _f(i,0,1)scanf("%s",s[i]+1);
    //尝试把上面作为联通线
    int step1=0,step2=0,step3=0;
    int uph=2e5+10,upt=-1,downh=2e5+10,downt=-1;//找到边界
    _f(i,1,n)if(s[0][i]=='*'){uph=i;break;}
    f_(i,n,1)if(s[0][i]=='*'){upt=i;break;}
    _f(i,1,n)if(s[1][i]=='*'){downh=i;break;}
    f_(i,n,1)if(s[1][i]=='*'){downt=i;break;}
    if(upt==-1&&downt==-1)
    {
        chu("0\n");
        return;
    }
    if(upt==-1)
    {
        chu("%d\n",downt-downh);
        return;
    }
    if(downt==-1)
    {
        chu("%d\n",upt-uph);
        return;
    }
    int bjl=min(uph,downh);
    int bjr=max(downt,upt);
    if(s[0][bjl]=='*'&&s[1][bjl]=='*')
    dp[0][bjl]=dp[1][bjl]=1;
    else if(s[0][bjl]=='*')dp[0][bjl]=0,dp[1][bjl]=1;
    else dp[1][bjl]=0,dp[0][bjl]=1;
    if(bjl==bjr)//特殊点
    {
        chu("1\n");
        return;//一定有2个在不同行,否则特判掉了
    }
    _f(i,bjl+1,bjr)
    {
        if(s[0][i]=='*')dp[1][i]=min(dp[0][i-1]+2,dp[1][i-1]+2);
        else dp[1][i]=min(dp[0][i-1]+2,dp[1][i-1]+1);
        if(s[1][i]=='*')dp[0][i]=min(dp[1][i-1]+2,dp[0][i-1]+2);
        else dp[0][i]=min(dp[1][i-1]+2,dp[0][i-1]+1);
       // chu("dp[%d][%d]:%d dp[%d][%d]:%d\n",0,i,dp[0][i],1,i,dp[1][i]);
    }
    chu("%d\n",min(dp[0][bjr],dp[1][bjr]));
}
int main()
{
   //freopen("challenge4.in","r",stdin);
  //  freopen("1.out","w",stdout);
    T=re();
    _f(i,1,T)
    {
        Deal();
       // if(i==14)chu("%s\n",s[0]+1),chu("%s",s[1]+1);
    }
    return 0;
}
/*
1
2
**
**
5
1
*
.
2
.*
**
3
*.*
.*.
4
**.*
**..
5
**...
...**



0
2
3
5
5
*/

T2【Z函数找前缀匹配优化+贪心优化DP转移】给你长度n的字符串,要求你选出若干个子串[l,r]满足每次新选择的子串l_new>l_last||r_new>r_last而且字典序严格>last.求最多选择多少个。(n<=5000)

考场

\(dp[i][j];表示选择i位置的串,选择了长度是j的的最多数量\)
\(dp[i][j]=max(dp[i][j-1],dp[x][y])满足[x,x+y-1]<[i,i+j-1]其中dp[x][y]是dp[x][y~n]的一段后缀,直接DP完传递一下就行\)

正解

只差一小步
已经考虑到如果选择了[i,j]那么一定会选择[i,j+1~n]的一段连续区间。
其实转移的时候就应该意识到如果i确定,from确定,那么转移点就已经确定了,就是i和from的一段连续前缀位置+1,如果s[pre_from]<s[pre_i]那么合法,这时候只能从i的i+pre+1的位置开始转移,而且from的所有子串都选更优,所以状态数量只有n个。\(O(n^2)\)

点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#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 inf 2147483647
#define ll long long 
#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=5100;
int dp[5100],Z[5100][5100];//dp[i];表示选择i位置的最多个数
//Z[i][j];表示在以i为起点意义下的j位置最长前缀
int T,n;
char s[N],ss[N];
inline void Deal()
{
    n=re();
    scanf("%s",ss+1);
    // Z[1]=n;
    // int Centre=0,R=0;
    // _f(i,2,n)
    // {
    //     Z[i]=min(R-i,Z[i-Centre+1]);
    //     while(i+Z[i]<=n&&s[Z[i]+1]==s[i+Z[i]])Z[i]++;
    //     if(i+Z[i]-1>R)R=i+Z[i]-1,Centre=i;
    // }
    _f(i,1,n)
    {
        int len=n-i+1;
        _f(j,i,n)s[j-i+1]=ss[j];
        Z[i][1]=len;
        int Centre=0,R=0;
        _f(j,2,len)
        {
            if(j<=R)Z[i][j]=min(R-j,Z[i][j-Centre+1]);
            while(j+Z[i][j]<=len&&s[Z[i][j]+1]==s[j+Z[i][j]])Z[i][j]++;
            if(i+Z[i][j]-1>=R)R=j+Z[i][j]-1,Centre=j;
        }
    }
    // _f(i,1,n)
    // {
    //     _f(j,1,n-i+1)chu("%d ",Z[i][j]);
    //     chu("\n");
    // }
    // return ;
    int ans=0;
    _f(i,1,n)//枚举现在选择哪个串,wait?  公共前缀?多个?没学过?每个都匹配一遍!
    {
        dp[i]=n-i+1;//最长,谁都不继承,就是自己全部
        _f(j,1,i-1)//枚举从哪里转移
        {
            int mach=Z[j][i-j+1];
            //相同的一段:[i]--[i+mach-1]   [j]--[j+mach-1]
         //   chu("%d front choose:%d  :%d\n",i,j,mach);
            if(i+mach<=n&&ss[j+mach]<ss[i+mach])//如果说j的字典序是够小的
            dp[i]=max(dp[i],dp[j]+n-i-mach+1);
        }
       // chu("dp[%d]:%d\n",i,dp[i]);
        ans=max(ans,dp[i]);
    }
    chu("%d\n",ans);
}
int main()
{
   // freopen("heaven6.in","r",stdin);
   // freopen("1.out","w",stdout);
    T=re();
    while(T--)
    {
        _f(i,0,n)
        {
            dp[i]=0;
            _f(j,0,n)Z[i][j]=0;
        }
        Deal();
    }
    return 0;
}
/*
7
5
acbac
8
acabacba
12
aaaaaaaaaaaa
10
abacabadac
8
dcbaabcd
3
cba
6
sparky
*/

T3【排列组合:Catalan数 灵活应用+非合数取模求组合数】骑士的血量初始值是1,你有n个选择机会,每次可以进行3种操作:[1]给骑士血量+1[2]血量-1[3]血量不变。给出[l,r]表示最终骑士的血量在[l,r]之间而且始终血量>0.求操作方案数%p(p<=2e9,n<=1e5)

转化成裸卡特兰数:
我们把[1]和[2]操作单独拿出来,作为“入栈”和“出栈”操作,[3]就是无效操作,确定了[1][2]的顺序之后,[3]的顺序可以随便插入,就是一个组合数\(C(n,i)\),假设[1]=x,[2]=y,那么\(x-y>=j而且x+y<=n-i\),设\(x+y=i,x-y=j\)那么理解成平面直角坐标系上你可以走i步,要求过程中横走步数>=竖着走步数,最终到达【x,y】的方案数(x>=y)。那么因为(x,y)关于y=x+1的对称点是y-1,x+1,所以就是\(C(i,(i+j)/2+1)\)
所以最终的答案就是
\(sigma_i(C(n,i)*sigma_j(C(n-i,(n-i-j)/2)-C(n-i,(n-i-j)/2-1)))\)发现因为要求\(2|(i+j)\)所以j每次+1,而且C的右边的数也是每次+1,那么展开后就可以消掉,最终只剩下
\(C(n-i,(n-i-l)/2)-C(n-i,(n-i-r-1)/2)\),注意这里的后面一项在运算的时候要>>1,因为如果r=l+1,那么实际是不存在一个值满足要求的,应该是0才对。
优化组合数计算在%合数意义下:
(1)对于求一个组合数,可以拆出C的所有质因数,消掉然后只剩下*的再取模\(O(lenthof Fac*log)\),不现实。
考虑把模数也拆成质因子形式,这是为了方便统计消除阶乘的结果和p的公因数,把阶乘也拆分成\(x*p1^x*p2^y...\)形式,其中\(gcd(x,p)=1\),那么阶乘运算可以分成2个部分
【1】互质部分,直接逆元计算,(注意用\(a^{phi(p)}同余a^{-1}mod p:gcd(a,p)=1\))
【2】不互质部分,直接质因子指数相减

点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#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 inf 2147483647
#define ll long long 
#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=1e5+100;
//预处理:
int pr[10],cnt;//pr是mod含有的质因子个数
ll f[N],inv[N];//n!%p和逆元
ll phi;//p的欧拉函数
int c[N][10];//n!中质因子出现次数
int n;ll mod,ls;
inline ll qpow(ll a,ll b)
{
    ll bas=1;
    while(b)
    {
        //chu("b:%d\n",b);
        if(b&1)bas=bas*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return bas;
}
inline void Prework()
{
    inv[0]=inv[1]=f[0]=f[1]=1;
  //  _f(i,1,cnt)chu("pr[%d]:%lld\n",i,pr[i]);
    _f(i,2,n)//最多涉及到
    {
       // chu("i:%d\n",i);
        ls=i;
        _f(j,1,cnt)
        {
            c[i][j]=c[i-1][j];
            while(ls%pr[j]==0&&ls!=1)++c[i][j],ls/=pr[j];
        }
        f[i]=f[i-1]*ls%mod;
        inv[i]=inv[i-1]*qpow(ls,phi-1)%mod;//逆元要连续吗?应该需要吧????
    }
   // chu("out\n");
}
inline ll C(int nr,int mr)
{
    if(nr<mr||nr<0||mr<0)return 0;
    if(mr==0)return 1;
    ll ans=f[nr]*inv[mr]%mod*inv[nr-mr]%mod;
   // chu("before:%lld\n",ans);
    _f(i,1,cnt)//一个一个筛质因子
    {
        ans*=qpow(pr[i],c[nr][i]-c[mr][i]-c[nr-mr][i]);
        ans%=mod;
    }
   // chu("C[%d,%d]:%lld\n",nr,mr,ans);
    return ans;
}
int main()
{
   // freopen("pill5.in","r",stdin);
    //freopen("1.out","w",stdout);
    n=re();mod=re();int l=re(),r=re();
    ll ans=0;
    ls=mod;phi=mod;
   // chu("out\n");
    for(ll i=2;i*i<=mod;++i)
    {
       //  chu("shai;%lld\n",i);
        if(ls%i)continue;
        phi=phi*(i-1)/i;
        pr[++cnt]=i;//chu("i:%d\n",i);
        while(ls%i==0&&ls!=1)ls/=i;
    }
   //_f(i,1,cnt)chu("%d \n",pr[i]);
   //chu("out\n");
    if(ls>1)phi=phi*(ls-1)/ls,pr[++cnt]=ls;
   // chu("ls:%d\n",ls);
    Prework();
   // chu("phi;%d\n",phi);
    _f(i,0,n-l)
    {
        ll tmp=(C(n-i,(n-i-l)/2)-C(n-i,(n-i-r-1)>>1)+mod)%mod;
        //chu("tmp:%lld\n",tmp);
      //  chu("dfsC(%d,%d):%lld\n",n-i,(n-i-l)>>1,C(n-i,(n-i-l)>>1));
      //  chu("ans from %lld*%lld\n",tmp,C(n,i));
        ans+=tmp*(C(n,i))%mod;
        ans%=mod;
    }
    chu("%lld",ans);
    return 0;
}
/*
4
97 
2
3
*/
posted on 2022-10-08 19:23  HZOI-曹蓉  阅读(16)  评论(1)    收藏  举报