暑假集训CSP提高模拟2
暑假集训CSP提高模拟2
唐完了哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
11/35...... 挂了 \(200\) 的样子?
T1 活动投票
- 正解
摩尔投票板子,用投票方式对抗出次数大于一半的数
赛时想了个crt做法,结果质数选太小全挂了,乐
我们发现只有答案出现次数大于一半,我们的空间不够全部读入,尝试舍弃部分信息,对于任意数 \(p\),求出所有数 \(\mod p\) 之后得到次数最多的结果,之后我们就知道了 \(ans \mod p\) 直接crt求解即可,赛时唐了两小时crt,我再也不D 数据删除 因为不会写crt挂分了
因为素数选小,常数巨大,全TLE,捆绑测试,遂挂 \(100\) 分,赛后随便换了几个大质数就过了
贴个代码先
#include<bits/stdc++.h>
#define llt long long
const llt N=101000;
using namespace std;
llt now,us[40][1054],n,l,ans[40],p[40],maxx[40],u,v,m;
vector<llt> prime;
llt exgcd(llt x,llt y,llt &a,llt &b)
{
if(x%y==0) {a=0,b=1;return y;}
llt gcd=exgcd(y,x%y,a,b),c=a;
a=b,b=c-a*(x/y);
return gcd;
}
void crt()
{
llt o;
for(int i=0;i<l-1;i++)
{
u=v=0;
o=exgcd(prime[i],prime[i+1],u,v);
u=(u%prime[i+1]+prime[i+1])%prime[i+1];
u=u*((ans[i+1]-ans[i])%prime[i+1]);
u=(u%prime[i+1]+prime[i+1])%prime[i+1];
m=u*prime[i]+ans[i];
ans[i+1]=m,prime[i+1]*=prime[i];
m%=prime[i+1];
}
printf("%lld\n",m);
}
int main()
{
#ifdef LOCAL
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
prime.push_back(1009);prime.push_back(1019);prime.push_back(1021);
l=prime.size();
prime.resize(100);
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&now);
for(int j=0;j<l;j++)
us[j][now%prime[j]]++;
}
for(int i=0;i<l;i++)
for(int j=0;j<prime[i];j++)
if(us[i][j]>maxx[i])
maxx[i]=us[i][j],ans[i]=j;
crt();
return 0;
}
T2 序列
感觉是一眼套路题,赛时想到的一个做法是扫描序列,对于一个数,如果是第一次出现,将权值加上 \(1\),否则加上权值的同时将上一个数的权值修改成 \(-1\),记得将上上个数清空,每次查询当前后缀等于 \(0\) 的个数,因为每个减法后面一定有对应的加法,所以这个后缀和一定 \(\ge 0\),线段树维护区间 \(\min\) 即可
场上莫名其妙写挂掉了,挂掉 \(70\)
T3 Legacy
线段树优化建图板子,简单来说就是建棵线段树,通过中转点来减少连边数,注意出树和入树只有叶子联通
赛时不会,写了 \(60\) 暴力,但实际得分为 \(20\)
T4 DP搬运工1
好题,难题。
这个好像叫预设型dp,我不会啊
首先发现枚举每一位复杂度是巨错无比的,这玩意因为优秀的阶乘复杂度荣获 \(20\) 分
我们发现可以dp,从小到大填数,你在填了这个数之后如果它的两端每有一个已经填过的数,当前数就会对那个 \(\max\) 产生贡献,这里不能从大到小的原因是两端不会产生贡献,如果从大到小做会多算两边的数
有一个显然的状压做法对吧,每一位填了就是 \(1\),否则是 \(0\)
之后我们发现我们其实关心的只有每个 \(a\),\(b\) 之间的位置关系的方案数,所以考虑优化,对于这样几种情况
1 1 1 0 1 1 1 0
1 1 1 0 0 1 1 1
1 1 1 1 0 0 1 1
他们的贡献相同时,合法方案数都是一样的(因为在端点处的数个数相同),两边互不影响,所以我们将类似这种的情况都当作一个状态
所以状态设计就是 当前填到哪个数 分了几个段 最终贡献是多少
现在开始写转移,新加入的数有以下几种可能
- 加到某个区间端点,但不接上另一个区间,贡献是其权值
- 加到某个区间端点,同时接上另一个区间,贡献是其权值 \(2\) 倍
- 加到中间,不和任何区间相邻,没贡献
方程自然有了
大力dp之后输出 \(\sum_{i=0}^k dp_{n,1,i}\) 即可,记得取模,初始化可以 \(dp_{1,1,0}=1\),复杂度 \(O(n^2k)\)
贴个代码:
#include<bits/stdc++.h>
#define llt long long
const llt mod=998244353;
using namespace std;
llt n,k,dp[60][60][2600],ans;
int main()
{
#ifdef LOCAL
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
scanf("%lld%lld",&n,&k);
dp[1][1][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int d=0;d<=k;d++)
{
if(d>=i) dp[i][j][d]+=dp[i-1][j][d-i]*2%mod*j%mod;
if(d>=2*i) dp[i][j][d]+=dp[i-1][j+1][d-2*i]*j%mod;
if(j>=1) dp[i][j][d]+=dp[i-1][j-1][d]*j%mod;
dp[i][j][d]%=mod;
}
}
}
for(int j=0;j<=k;j++)
ans+=dp[n][1][j],ans%=mod;
printf("%lld\n",ans);
return 0;
}