模拟51 考试总结
小 丑 就 是 我 自 己
考试经过
据说和好多大佬一起考试,害怕+=inf,说要写文件,担心会爆零。。。
T1一看发现像是dp,但看到数据范围觉得不像,就像直接一个柿子线性球出来,看了半天不会……
T2数学题,很快写出来柿子,一通乱推之下做到了\(O(n)\),又卡了半天常,发现根本不用,淦
T3有想法但是思路比较乱,T4一看NPC?,打了暴力但是没多少分,T1暴力比正解难打就没写,最后10min冲T3线段树+单调栈,无果,嘎
0+100+10+5=115,12个切两个的,菜斩了
发现T1是sbdp,想到昨天刚写了一个类似的博客,人傻了
T1.茅山道术
实际上是一个很水的dp,然而考场上智障没往dp想
发现如果一段区间颜色相同,选了也白选,因此只考虑最近的
设\(f_i\)表示考虑前\(i\)个位置,加入一个考虑上一个颜色相同的点在哪里,设其位置为\(j\)
如果\(j\)没有,直接继承\(f_i=f_{i-1}\)
如果有的话要看上一个位置在哪里,要与他相差大于1才有贡献\(f_i=f_{i+1}+[i-j>1]\times f_j\)
#include <bits/stdc++.h>
using namespace std;
const int N=2000050;
const int mod=1e9+7;
int f[N],a[N],last[N],now[N];
signed main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
int n;cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
last[i]=now[a[i]];
now[a[i]]=i;
}
f[0]=1;
for(int i=1;i<=n;i++)
{
if(!last[i])f[i]=f[i-1];
else f[i]=(1ll*f[i-1]+(i-last[i]>1?f[last[i]]:0))%mod;
}
cout<<f[n]<<endl;
return 0;
}
T2.泰拳警告
考场唯一做出来的题,可以直接推柿子,每次枚举平局
这样是\(n^2\),瓶颈主要在后面,发现后面就是杨辉三角的半行,直接不太好求,考虑\(O(n)\)递推预处理
下一个\(sum\)就是上一个乘上2再加减上一列的末尾项,可以根据杨辉三角的产生方式得到,所以就\(O(n)\)了
#include <bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=3000050;
inline int ksm(int x,int y)
{
int s=1;
for(;y;y>>=1)
{
if(y&1)s=1ll*s*x%mod;
x=1ll*x*x%mod;
}
return s;
}
inline int ny(int x){return ksm(x,mod-2);}
int jc[N],inv[N],jcny[N],p1[N],p2[N],bit[N],sum[N];
inline int C(int x,int y)
{
if(x<y)return 0;
if(!y)return 1;
return 1ll*jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
signed main()
{
freopen("fight.in","r",stdin);
freopen("fight.out","w",stdout);
int n,p;cin>>n>>p;
jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
for(int i=2;i<=n+5;i++)
{
jc[i]=1ll*jc[i-1]*i%mod;
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
jcny[i]=1ll*jcny[i-1]*inv[i]%mod;
}
int pp1=1ll*p*ny(p+2)%mod,pp2=ny(p+2);
p1[0]=1;p2[0]=1;
for(int i=1;i<=n;i++)
{
p1[i]=1ll*p1[i-1]*pp1%mod;p2[i]=1ll*p2[i-1]*pp2%mod;
sum[i]=1ll*sum[i-1]*2%mod;
if(i&1)sum[i]=(1ll*sum[i]+C(i-1,i/2))%mod;
else sum[i]=(1ll*sum[i]-C(i-1,i/2-1)+mod)%mod;
}
int ans=0;
for(int i=0;i<n;i++)ans=(1ll*ans+1ll*(i+1)*C(n,i)%mod*p1[i]%mod*p2[n-i]%mod*sum[n-i]%mod)%mod;
cout<<ans<<endl;
return 0;
}
T3.万猪拱塔
毒瘤题,基本属于出题人yy
发现有一些性质,\(w\)互不相同这个比较奇怪,思路也就从这里找
可以转化问题,判定对于一个值域的区间\([l.r]\),它所在的格子是否构成矩形
然后就是鬼能想到的构造方式,将整个网格分成\((n+1)\times (m+1)\)个\(2\times 2\)的小正方形,部分出界也算,然后将区间内权值对应的格子染黑,这个区间合法当且仅当仅有4个小正方形内部有1个黑格,并且没有小正方形内部有3个黑格
这种判定方式属实不是给人想的,不过也不难理解

这就是实现方法,用线段树,维护最小值每次对最小值维护附加信息
考虑修改是\(r\)新加了一个,会有4个小正方形受影响,根据他们内部的\(w\)与当前\(r\)对应的\(w\)的大小关系,对于每个小正方形可以分成有0个,1个,2个,3个格子的\(w\)比当前的小,然后大力分类讨论,就是区间加减1的操作,弄清楚哪一段加哪一段减
一个技巧是把超出边界的都设成正无穷,就不用特判了
最后区间查询,只有最小值等于4的时候才会累加答案,复杂度\(n\log n\)
#include <bits/stdc++.h>
using namespace std;
//#define int long long
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=200050;
const int mod=998244353;
inline int addd(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int doit(int x){return x>=mod?x-mod:x;}
struct tree{
int l,r,mi,lazy,s1,s2;
}tr[4*N];
inline void qi(int id)
{
tr[id].mi=min(tr[id<<1].mi,tr[id<<1|1].mi);
if(tr[id<<1].mi<tr[id<<1|1].mi)tr[id].s1=tr[id<<1].s1,tr[id].s2=tr[id<<1].s2;
else if(tr[id<<1].mi>tr[id<<1|1].mi)tr[id].s1=tr[id<<1|1].s1,tr[id].s2=tr[id<<1|1].s2;
else tr[id].s1=tr[id<<1].s1+tr[id<<1|1].s1,tr[id].s2=addd(tr[id<<1].s2,tr[id<<1|1].s2);
}
void build(int id,int l,int r)
{
tr[id].l=l;tr[id].r=r;
if(l==r)
{
tr[id].mi=0;tr[id].s1=1;
tr[id].s2=l;return;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);build(id<<1|1,mid+1,r);
qi(id);
}
inline void luo(int id)
{
if(tr[id].l!=tr[id].r&&tr[id].lazy)
{
tr[id<<1].lazy+=tr[id].lazy;
tr[id<<1|1].lazy+=tr[id].lazy;
tr[id<<1].mi+=tr[id].lazy;
tr[id<<1|1].mi+=tr[id].lazy;
tr[id].lazy=0;
}
}
void add(int id,int l,int r,int v)
{
if(l<=tr[id].l&&r>=tr[id].r)
{
tr[id].mi+=v;
tr[id].lazy+=v;return;
}
luo(id);int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid)add(id<<1,l,r,v);
else if(l>mid)add(id<<1|1,l,r,v);
else add(id<<1,l,mid,v),add(id<<1|1,mid+1,r,v);
qi(id);
}
struct q{int mi,s1,s2;};
q get(int id,int l,int r)
{
if(l<=tr[id].l&&r>=tr[id].r)return (q){tr[id].mi,tr[id].s1,tr[id].s2};
luo(id);int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid)return get(id<<1,l,r);
if(l>mid)return get(id<<1|1,l,r);
q an1=get(id<<1,l,mid),an2=get(id<<1|1,mid+1,r),ans;
ans.mi=min(an1.mi,an2.mi);
if(an1.mi<an2.mi)ans.s1=an1.s1,ans.s2=an1.s2;
else if(an1.mi>an2.mi)ans.s1=an2.s1,ans.s2=an2.s2;
else ans.s1=an1.s1+an2.s1,ans.s2=addd(an1.s2,an2.s2);
return ans;
}
vector <int> a[N];
int px[N],py[N];
inline void gan(int x,int y,int z,int p)
{
int s=(x<p)+(y<p)+(z<p);
if(s==0)add(1,1,p,1);
else if(s==1)
{
int ga=(x<p)?x:(y<p?y:z);
add(1,1,ga,-1);add(1,ga+1,p,1);
}
else if(s==2)
{
int l=(x<p)?x:y,r=((l==y)?z:min(y,z));if(l>r)swap(l,r);
add(1,1,l,1);add(1,l+1,r,-1);add(1,r+1,p,1);
}
else
{
if(x>y)swap(x,y);if(x>z)swap(x,z);if(y>z)swap(y,z);
add(1,1,x,-1);add(1,x+1,y,1);add(1,y+1,z,-1);add(1,z+1,p,1);
}
}
signed main()
{
freopen("pig.in","r",stdin);
freopen("pig.out","w",stdout);
int n,m;cin>>n>>m;
for(int i=0;i<=m+1;i++)a[0].push_back(1e9);
for(int i=1;i<=n;i++)
{
a[i].push_back(1e9);
for(int j=1;j<=m;j++)
{
int x=read();
a[i].push_back(x);
px[x]=i;py[x]=j;
}
a[i].push_back(1e9);
}
for(int i=0;i<=m+1;i++)a[n+1].push_back(1e9);
int ans=0;build(1,1,n*m);
for(int i=1;i<=n*m;i++)
{
int x=px[i],y=py[i];
gan(a[x-1][y-1],a[x][y-1],a[x-1][y],i);
gan(a[x-1][y+1],a[x][y+1],a[x-1][y],i);
gan(a[x+1][y-1],a[x][y-1],a[x+1][y],i);
gan(a[x+1][y+1],a[x][y+1],a[x+1][y],i);
q an=get(1,1,i);
if(an.mi==4)ans=addd(doit(ans-an.s2+mod),1ll*an.s1*(i+1)%mod);
}
cout<<ans<<endl;
return 0;
}
长见识了
T4.抑郁刀法
也是一道好题,比较神
首先直接dfs的复杂度是和\(k\)有关的,所以基本没分
发现最多只会染出\(n\)种颜色,所以可以用组合数算出最终答案,这样复杂度就与\(k\)无关了
于是可以状压,设\(f_{s,i}\)表示当前已经染色的点集合是\(s\),用了\(i\)种颜色,转移时枚举\(s\)的补集的子集,将它们染成另外一种颜色,显然当且仅当这个子集中内部没有连边才是合法的,大体的转移方程就是\(dp_{s|k,i+1}+=dp_{s,i}\times [check(k)]\)
现在n急剧上升到\(10^5\),这种思路看来并没有扩展性,然而题目已经告诉你这是npc,似乎找不到多项式复杂度做法
再审一遍题,发现有一个奇怪的条件\(m<=n+5\),这个似乎很离谱,但恰恰是本题的突破口
考虑一个点,如果他度数为1,那么可以直接把它从图上删去,答案会累加\(k-1\)的贡献,正确性显然
知道这个往下想,如果度数为2怎么办?
然后就开始神仙构造了,对每个边赋两个权值\(f,g\),分别表示两端的点同色,异色的时候的方案数,一开始显然有\(f=0,g=1\)
那么上面的转移可以写成
其中\(r\)代表边,先枚举\(k\)集合中的边,钦定他们都相同,然后枚举\(s\)到\(k\)的边,钦定他们不同
如果一个点度数为2,那么可以缩点,将这个点删去,然后在他连的两个点之间连一条新边,新的\(f,g\)就是
画图理解一下,分别考虑删去的这个点颜色与两边的相同还是不同,讨论一下,不难理解
如果我们一直重复这个过程,最后所有点的度数都大于等于3,因为\(m<=n+5\),所以最后剩下的点数不超过10,就可以状压了
因为\(f,g\)的存在,将一个点直接删去时的贡献是\((k-1)\times g+f\),也是分别计算相同和不同
过程中可能会出现重边,可以直接将重边缩到一起,新的\(f,g\)分别是原来的\(f,g\)相乘。对于最后点缩没了(三元环)特判一下
实现方式比较多,但应该都比较复杂,我使用了map+堆实现缩点操作,复杂度\(n\log m\),理论上瓶颈在状压上,实际跑的很快
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=100050;
map <int,pair<int,int> > mp[N];
priority_queue <pair<int,int> > q;
struct node{
int from,to,next,f,g;
}a[40];
int head[40],mm=1;
inline void add(int x,int y,int ff,int gg)
{
a[mm].from=x;a[mm].to=y;
a[mm].f=ff,a[mm].g=gg;
a[mm].next=head[x];head[x]=mm++;
}
int n,m,k,du[N];bool de[N];
vector <int> sta[20];
inline int getsum(int x)
{
int s=0;
while(x)
{
if(x&1)s++;
x>>=1;
}
return s;
}
int dp[1<<15][15],mpp[N],tot;
int inv[N],jc[N],jcny[N];
inline int C(int x,int y)
{
if(x<y)return 0;
if(!y)return 1;
return jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
signed main()
{
freopen("knife.in","r",stdin);
freopen("knife.out","w",stdout);
cin>>n>>m>>k;
jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
for(int i=2;i<=k;i++)
{
jc[i]=jc[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
jcny[i]=jcny[i-1]*inv[i]%mod;
}
for(int i=1;i<=m;i++)
{
int x,y;scanf("%lld%lld",&x,&y);
mp[x].insert(make_pair(y,make_pair(0,1)));du[x]++;
mp[y].insert(make_pair(x,make_pair(0,1)));du[y]++;
}
int sum=1;
for(int i=1;i<=n;i++)q.push(make_pair(-du[i],i));
while(q.size())
{
int w=-q.top().first,x=q.top().second;q.pop();
if(w>=3)break;if(!du[x])continue;
if(w==1)
{
auto p=*mp[x].begin();int y=p.first;
int f=p.second.first,g=p.second.second;
mp[y].erase(x);mp[x].erase(y);
q.push(make_pair(-(--du[y]),y));
du[x]=0;sum=((k-1)*g%mod+f)%mod*sum%mod;
}
if(w==2)
{
auto p1=*mp[x].begin(),p2=*mp[x].rbegin();
int y1=p1.first,f1=p1.second.first,g1=p1.second.second;
int y2=p2.first,f2=p2.second.first,g2=p2.second.second;
du[x]=0;mp[x].clear();mp[y1].erase(x);mp[y2].erase(x);
int f=(f1*f2%mod+(k-1)*g1%mod*g2%mod)%mod;
int g=((k-2)*g1%mod*g2%mod+f1*g2%mod+f2*g1%mod)%mod;
auto ga=mp[y1].find(y2);
if(ga!=mp[y1].end())
{
auto p=*ga;int ff=p.second.first,gg=p.second.second;
int fff=f*ff%mod,ggg=g*gg%mod;
mp[y1][y2]=make_pair(fff,ggg);
mp[y2][y1]=make_pair(fff,ggg);
q.push(make_pair(-(--du[y1]),y1));
q.push(make_pair(-(--du[y2]),y2));
}
else
{
mp[y1].insert(make_pair(y2,make_pair(f,g)));
mp[y2].insert(make_pair(y1,make_pair(f,g)));
}
}
}
int newsum=0,ans=0;
for(int i=1;i<=n;i++)
{
if(!du[i])continue;
if(!mpp[i])mpp[i]=++tot;
for(auto x:mp[i])
{
int y=x.first,ff=x.second.first,gg=x.second.second;
if(!mpp[y])mpp[y]=++tot;
add(mpp[i],mpp[y],ff,gg);
}
newsum++;
}
n=newsum;if(!n){cout<<sum*k%mod<<endl;return 0;}
for(int i=0;i<(1<<n);i++)sta[getsum(i)].push_back(i);
dp[0][0]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<sta[i].size();j++)
{
int s=sta[i][j];
for(int p=0;p<=i;p++)
{
if(!dp[s][p])continue;
int sp=s^((1<<n)-1);
for(int ss=sp;ss;ss=(ss-1)&sp)
{
int f=1,g=1;
for(int l=1;l<=n;l++)
{
if(!((ss>>(l-1))&1))continue;
for(int ii=head[l];ii;ii=a[ii].next)
{
int y=a[ii].to;
if(!((ss>>(y-1))&1))continue;
if(y<l)continue;
f=f*a[ii].f%mod;
}
}
for(int l=1;l<=n;l++)
{
if(!((s>>(l-1))&1))continue;
for(int ii=head[l];ii;ii=a[ii].next)
{
int y=a[ii].to;
if(!((ss>>(y-1))&1))continue;
g=g*a[ii].g%mod;
}
}
dp[s|ss][p+1]=(dp[s|ss][p+1]+dp[s][p]*f%mod*g%mod)%mod;
}
}
}
}
for(int i=1;i<=n;i++)ans=(ans+dp[(1<<n)-1][i]*C(k,i)%mod)%mod;
cout<<ans*sum%mod<<endl;
return 0;
遇到npc的图论时想办法把数据范围变小,本质是化归
考试总结
1.不要给自己设限,能想到的都要想到,简单题不要失分
2.对于新奇思路多积累,做一道就有一道,理解要尽量深入
3.留意一些奇怪的数据范围和限制条件,很可能是关键所在

浙公网安备 33010602011771号