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
*/
浙公网安备 33010602011771号