T1T2很水........
我中间回去听课少做了1个多小时依然200分........
然而T3并没有人做出来(集体T3爆0hhhh)
后来发现代码巨tm短........
然后搞了半天才懂
T1
考场上在周围人在写dfs打表观察规律的时候我花了20秒切掉了这道题........
我们考虑到,对于每一个颜色的珠子来说,会影响它位置的只有上一个颜色的珠子,而我们的要求中只要最后一个位置在前面一个颜色的最后一个位置的右边就好了
所以我们考虑dp
定义dp[i]为把前i种颜色放成一个合法序列的方案数
然后我们会发现它的状态实际上只会由dp[i-1]转移过来
我们在这次第i中种颜色的所有珠子(设有a[i]个)内拿一个出来放在最后一个(固定位置),那么我们会发现另外的a[i]-1个珠子是可以随便放在任何位置上的。
而假设前i种颜色的总数为sum,加上这次的a[i]-1个,总共就是sum+a[i][-1个珠子,而我们可以将这之中a[i]-1个到处乱放
所以对于dp[i-1]中的每一种摆放方法,我们都C(sum+a[i]-1,a[i]-1)种放法
所以说
dp[i]=dp[i-1]*C(sum+a[i]-1,a[i]-1);
所以求一个逆元就可以O(n)跑了
#include<cstdio>
#define OP "qiang"
#define LL long long
const int MAXN=1e5+5;
const int MOD=998244353;
int n;
LL fac[MAXN*5];
LL inv[MAXN*5];
void exgcd(int a,int b,LL &x,LL &y)
{
if(!b)
{
x=1;
y=0;
return;
}
else
{
exgcd(b,a%b,y,x);
y-=(a/b)*x;
}
}
LL inverse(int a,int MOD)
{
LL x,y;
exgcd(a,MOD,x,y);
return (x%MOD+MOD)%MOD;
}
void init()
{
fac[0]=1;
inv[0]=1;
for(int i=1;i<=500000;i++)
{
fac[i]=1ll*i*fac[i-1]%MOD;
}
inv[500000]=inverse(fac[500000],MOD);
for(int i=500000-1;i>=1;i--)
{
inv[i]=1ll*(i+1)*inv[i+1]%MOD;
}
}
LL C(int n,int m)
{
return 1ll*fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int a[MAXN];
LL dp[MAXN];
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
std::scanf("%d",&n);
if(n==1)
{
std::printf("1\n");
return 0;
}
init();
for(int i=1;i<=n;i++)
{
std::scanf("%d",a+i);
}
dp[1]=1;
int sum=a[1];
for(int i=2;i<=n;i++)
{
if(a[i]==1)
{
dp[i]=dp[i-1];
}
else
{
dp[i]=1ll*dp[i-1]*C(sum+a[i]-1,a[i]-1)%MOD;
}
sum+=a[i];
}
std::printf("%lld\n",dp[n]);
return 0;
}
T2
作为骨牌覆盖3道题目的最终版本......
看到的时候就秒杀矩阵快速幂...........
我们先考虑棋盘m*n的情况 ,当m<=11,n<=11的时候我们可以直接状压处理,(轮廓线经典题目)
当m<=8andn<=1e5的时候
我们可以状压m,然后n一直for过去......
那么这道题目作为最终版本。相当于m==4,n==1e9;
我们的做法是考虑使用状态压缩的思想,然后用矩阵快速幂优化转移
因为我们考虑到对于每一次的dp状态来说,它可以转移到的状态一定相同而且每次的方程完全一样
那么问题就被转换成为了一个线性递推的问题
那么我们就可以用熟悉的矩阵快速幂优化线性递推的方式优化dp的转移(这个技巧在任何转移方程相同的dp转移上都可以使用)
直接预先处理出转移矩阵,然后矩阵快速幂裸题..........
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "count10"
const int MAXN=(1<<7)+5;
int k=4,n,MOD;
int zy[MAXN][MAXN];//转移矩阵/初始状态
int now[MAXN][MAXN];//当前状态,用于自己乘自己的状态
int g[MAXN][MAXN];//转移的中间矩阵,储存临时变量
void find(int x,int y,int col) //搜索得到转移矩阵/初始状态
{
if(col==k)
{
zy[y][x]=1;
return;
}
find(x<<1,y<<1|1,col+1);// 不放
find(x<<1|1,y<<1,col+1); //竖放
if(col+2<=k)
{
find((x<<2)+3, (y<<2)+3, col+2);//横放,放了过后把皮怪的状态交给下下列处理,特判边界
}
}
void cheng(int a[][MAXN],int b[][MAXN])//矩阵乘法
{
for(int i=0;i<(1<<k);i++)
{
for(int j=0;j<(1<<k);j++)
{
int tmp=0;
for(int kk=0;kk<(1<<k);kk++)
{
tmp=(tmp+1ll*a[i][kk]*b[kk][j]%MOD)%MOD;
}
g[i][j]=tmp;
}
}
std::memcpy(a,g,sizeof(g));//将过程矩阵拷贝至原位置
}
int mpow(int n)
{
//LL rt=1;
while(n)
{
if(n&1)
{
cheng(zy,now);
/*
rt=rt*a%MOD;
*/
}
cheng(now,now); //矩阵自乘
/*
a=1ll*a*a%MOD;
*/
n>>=1;
}
return zy[(1<<k)-1][(1<<k)-1]%MOD; //输出系数
}
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
while(1)
{
std::memset(zy,0,sizeof(zy));
std::memset(g,0,sizeof(g));
std::memset(t,0,sizeof(t));
std::memset(now,0,sizeof(now));
std::scanf("%d%d",&n,&MOD);
find(0,0,0);//得到转移矩阵
std::memcpy(now,zy,sizeof(zy));
if(!n&&!MOD)
{
break;
}
std::printf("%d\n",mpow(n-1)%MOD);
}
return 0;
}
T3
大贪心......最后的实际思路也并不是很好理解
我们发现,对于一个单调不降或者单调不增的序列来说,他们的花费一定是这个区间花费最多的那一个
那么我们考虑记录每一个ai转换乘bi的实际花费
然后我们会发现在这之间,会有很多区间是相互交错的
那么我们就考虑记录每一个区间如果往右边拓展(相当于跨越一个断点)的 花费
记录下当前位置区间从第一个拓展到现在的花费数量,装在桶里面统计贡献
由于我们需要尽量少的,那么我们直接取桶里面最小的那个就好
其实直接这么说并不如看代码清晰..........(这道题真的不是很好讲.........)
#include<cstdio>
#include<cstring>
#define OP "sequence"
const int MAXN=1e5+5;
int a[MAXN];
int b[MAXN];
int s[4];
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
int T;
std::scanf("%d",&T);
while(T--)
{
int n;
std::scanf("%d",&n);
for(int i=1;i<=n;i++)
{
std::scanf("%d",a+i);
}
for(int i=1;i<=n;i++)
{
std::scanf("%d",b+i);
a[i]=(b[i]-a[i]+4)%4;
}
int ans=a[1],j=0;
memset(s,0,sizeof s);
for(int i=2;i<=n;i++)
{
s[(4+a[i]-a[i-1]-1)%4+1]++;
if(a[i-1]>=a[i]) continue;
for(j=0;!s[j];j++);
s[j]--;
ans+=j;
}
printf("%d\n",ans);
}
return 0;
}
/*
1
5
2 1 3 0 3
2 2 0 1 0
*/