NOIP提高组模拟赛16
A. 茅山道术
简单\(DP\),只考虑相邻两个同色的是否使用就行
code
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1000005;
const int mod=1e9+7;
int rem[maxn],c[maxn],las[maxn],pos[maxn],n;
bool vis[maxn];
int work(int x){
if(x>n)return 1;
if(vis[x])return rem[x];
vis[x]=1;
if(pos[x]&&pos[x]!=x+1)return rem[x]=(1ll*work(pos[x])+work(x+1))%mod;
return rem[x]=work(x+1);
}
int main(){
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&c[i]);
for(int i=n;i>=1;--i){
pos[i]=las[c[i]];
las[c[i]]=i;
}
int aas=work(1);
printf("%d\n",aas);
return 0;
}
B. 泰拳警告
这题不难,如果你知道组合数的一些性质会更加简单,不会像我一样还要想垃圾\(dp\)
期望=概率*得分=总得分/总方案数
把平局看成\(p\)种方案,这样算出总得分最后乘以\((p+2)^n\)的逆元即可
得分与平局数相关,考虑枚举平局数\(k\),假设我们知道剩下\(n-k\)轮中,只考虑胜负,胜局多于负局的方案数\(f[n-k]\)
那么总得分为\(\sum_{k=0}^n (k+1)\times f[n-k]\times C_n^k\)(可以在\(n\)轮中任意\(k\)轮平局,所以需要乘上\(C_n^k\))
剩下的问题变成求解\(f[n-k]\)
不妨求\(f[i]\)
当前这一轮只有两种情况胜或负
如果当前胜,那么需要知道前\(i-1\)轮 胜场\(>=\)负场 的方案数
也就是 \(f[i-1]+\)前\((i-1)\)轮 胜场等于负场 的方案数
如果\(i-1\)是偶数那么后者的方案数就是\(C_{i-1}^{(i-1)/2}\),如果\(i-1\)是奇数,那么不存在后者
如果当前负,那么需要知道前\(i-1\)轮,胜场\(>\)负场\(+1\) 的方案数
也就是 \(f[i-1]-\)前\((i-1)\)轮 胜场 等于 负场\(+1\) 的方案数
如果\(i-1\)是奇数那么后者的方案数就是\(C_{i-1}^{(i-1)/2}\),如果\(i-1\)是偶数,那么不存在后者
这样就能解出\(f[]\)了
code
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=3000005;
const int mod=998244353;
int jc[maxn],inv[maxn],n,p;
int qpow(int x,int y){
int ans=1;
while(y){
if(y&1)ans=1ll*ans*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return ans;
}
void ycl(){
jc[0]=jc[1]=inv[0]=1;
for(int i=2;i<=n;++i)jc[i]=1ll*jc[i-1]*i%mod;
inv[n]=qpow(jc[n],mod-2);
for(int i=n-1;i>=1;--i)inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
int get_C(int n,int m){return 1ll*jc[n]*inv[n-m]%mod*inv[m]%mod;}
int f[maxn];
int main(){
freopen("fight.in","r",stdin);
freopen("fight.out","w",stdout);
scanf("%d%d",&n,&p);
ycl();
f[1]=f[2]=1;
for(int i=3;i<=n;++i){
f[i]=1ll*f[i-1]*2%mod;
if(i&1) f[i]=(1ll*f[i]+get_C(i-1,(i-1)>>1))%mod;
else f[i]=(1ll*f[i]-get_C(i-1,i>>1)+mod)%mod;
}
int ans=0,pk=1;
for(int k=0;k<=n;++k){
ans=(ans+1ll*get_C(n,k)*pk%mod*f[n-k]%mod*(k+1)%mod)%mod;
pk=1ll*pk*p%mod;
}
int in=qpow(p+2,n);
in=qpow(in,mod-2);
ans=1ll*ans*in%mod;
printf("%d\n",ans);
return 0;
}
C. 万猪拱塔
奇妙的线段树维护
发现符合要求的子矩阵一定是值域连续的,然后上题解。。。
考虑判定值在区间 \([l,r]\) 内的所有数所在的格子是否形成了一个矩形,记这些
格子的颜色为黑色,其它的格子颜色为白色。
考虑所有的 \((n + 1) ×(m + 1)\)个\(2 ×2\)的小正方形 (部分超出边界也算),则所有
黑色格子形成一个矩形,当且仅当恰好有 \(4\) 个小正方形内部有 \(1\) 个黑色格子,
并且没有任何一个小正方形内部有 \(3\) 个黑色格子
从小到大枚举 \(r\) ,对每个\(l ≤r\),记 \(f(l)\) 表示染黑权值在 \([l,r]\)内的格子后,有多
少小正方形内部有\(1\)个或\(3\)个黑色格子
可以发现有 \(f(l) ≥4\),\(f(r) = 4\) ,于是只需要对每个 \(l\) 维护 \(f(l)\) 最小值,最小值的
数目和取得最小值的所有 \(l\) 之和
每次\(r\) 增加\(1\) 时,会影响到周边的\(4\) 个\(2 ×2\) 的小正方形,在线段树上修改即可
时间复杂度 \(O(nm log nm)\)
code
#include <cstring>
#include <cstdio>
#include<algorithm>
#include<unordered_map>
typedef long long ll;
using namespace std;
void swap(int &x,int &y){x^=y;y^=x;x^=y;}
ll min(ll x,ll y){return x<y?x:y;}
ll max(ll x,ll y){return x>y?x:y;}
const int maxn=200005;
const int mod=998244353;
unordered_map<ll,ll>mp[maxn];
struct node{ll cnt,lazy,sum,min;}ret;
int n,m;
struct tree{
node t[maxn<<2|1];
void built(int x ,int l,int r){
if(l==r){
t[x].cnt=1;
t[x].sum=l;
return;
}
int mid=(l+r)>>1;
built(x<<1,l,mid);
built(x<<1|1,mid+1,r);
}
void push_up(int x){
t[x].sum=t[x].cnt=0;
t[x].min=min(t[x<<1].min,t[x<<1|1].min);
if(t[x].min==t[x<<1].min)t[x].cnt+=t[x<<1].cnt,t[x].sum+=t[x<<1].sum;
if(t[x].min==t[x<<1|1].min)t[x].cnt+=t[x<<1|1].cnt,t[x].sum+=t[x<<1|1].sum;
}
void push_down(int x){
t[x<<1].lazy+=t[x].lazy;
t[x<<1|1].lazy+=t[x].lazy;
t[x<<1].min+=t[x].lazy;
t[x<<1|1].min+=t[x].lazy;
t[x].lazy=0;
}
void modify(int x,int l,int r,int L,int R,ll z){
if(L>R)return;
if(L<=l&&r<=R){
t[x].lazy+=z;
t[x].min+=z;
return;
}
int mid=(l+r)>>1;
if(t[x].lazy)push_down(x);
if(L<=mid)modify(x<<1,l,mid,L,R,z);
if(R>mid)modify(x<<1|1,mid+1,r,L,R,z);
push_up(x);
}
void query(int x,int l,int r,int L,int R){
if(L<=l&&r<=R){
if(t[x].min==4){
ret.sum+=t[x].sum;
ret.cnt+=t[x].cnt;
}
return;
}
if(t[x].lazy)push_down(x);
int mid=(l+r)>>1;
if(L<=mid)query(x<<1,l,mid,L,R);
if(R>mid)query(x<<1|1,mid+1,r,L,R);
}
}T;
struct tpp{int x,y;}tp[maxn];
int ls[25];
void work(int x,int y,int w){
ll cnt=0,p,k=1;
ls[++cnt]=mp[x][y];
ls[++cnt]=mp[x-1][y-1];
ls[++cnt]=mp[x-1][y];
ls[++cnt]=mp[x][y-1];
sort(ls+1,ls+cnt+1);
p=lower_bound(ls+1,ls+cnt+1,w)-ls;//防止更新非法状态
for(p;p>=1;--p,k*=-1)T.modify(1,1,n,ls[p-1]+1,ls[p],k);
}
int main(){
freopen("pig.in","r",stdin);
freopen("pig.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<=n+1;++i)mp[0][i]=mp[n+1][i]=mp[i][0]=mp[i][n+1]=maxn;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
scanf("%d",&mp[i][j]);
tp[mp[i][j]].x=i;tp[mp[i][j]].y=j;
}
n=n*m;
T.built(1,1,n);
long long ans=0;
for(int i=1;i<=n;++i){
//对受影响的四个矩形进行操作
work(tp[i].x,tp[i].y,i);
work(tp[i].x+1,tp[i].y+1,i);
work(tp[i].x+1,tp[i].y,i);
work(tp[i].x,tp[i].y+1,i);
//查询当前贡献
T.query(1,1,n,1,i);
ans=(ans+(i+1)*ret.cnt-ret.sum+mod)%mod;
ret.cnt=ret.sum=0;
}
printf("%lld\n",ans);
return 0;
}
D. 抑郁刀法
不会。这题给我的唯一收获--了解了什么是\(NPC\)问题
粘一下题解
考虑对图进行收缩,对于度数为 \(1\)的点,可以直接将其删掉,并将最后答案乘
上 \((k −1)\)
对于度数为 \(2\) 的点,记它连向的两个点分别为\(a,b\)
将这个点删掉,若最后 \(a,b\) 之间同色,则有 \(k −1\) 种情况它与 \(a,b\) 都异色,有 \(1\)
种情况它与 \(a,b\) 都同色
若 \(a,b\) 之间异色,则有 \(k −2\) 种情况它与 \(a,b\) 都异色,有\(1\)种情况与\(a\)同色,\(1\) 种情况与 \(b\) 同色
我们为每两个点之间记录\(f(a,b),g(a,b)\)分别表示它们同色时答案乘上的系数,异色时答案乘上的系数。
在删掉度数为\(2\)的点时更新对应的 \(f(a,b)\) 即可
最终得到的图中,每个点度数至少为 \(3\) ,结合 \(m ≤n + 5\) 可算出 \(n ≤10,m ≤15\)
对这个图做状压 \(dp\) ,并考虑上 \(f,g\) 的系数即可
状压 \(dp\) ,每次选择一个未染色的子集进行染色,时间复杂度 \(O(nm3^n)\)
code