冲刺国赛模拟 12
写题
原题 P4967 黑暗打击。签到题,而且看着就很原。
考虑直接做,不会做。观察得到一个分形是两个上个分形正着加上两个上个分形横过来。考虑把希尔伯特曲线横过来,算它能淹没多少。设第 \(n\) 个正着是 \(a_n\),横着是 \(b_n\)。那么我们可以递推了。
- 对于正着:两个正着 + 两个横着 + 中间的三列:\(a_n=2a_{n-1}+2b_{n-1}+3\times 2^{n-1}-2\)。
- 对于横着:是一个正着 + 两个横着 + 中间的一列:\(b_n=a_{n-1}+2b_{n-2}+2^{n-1}-1\)。
接下来的问题就是怎么算这个递推式。上下相减得到
\[a_n-b_n=a_{n-1}+2^{n-1}-1
\]
\[b_n=a_n-a_{n-1}-2^{n-1}+1
\]
\[b_{n-1}=a_{n-1}-a_{n-2}-2^{n-2}+1
\]
代入上边的
\[a_n=4a_{n-1}-2a_{n-2}+2^{n-1}
\]
考虑把右边的 \(2^n\) 扔到 \(a_n\) 里边:拼一下左右系数可以设 \(f_n=a_n+2^n\),即有 \(f_n=4f_{n-1}+2f_{n-2}\)。解特征根快速幂即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define int long long
using namespace std;
const int mod=9223372036854775783,sqrt2=3689348813882916854,inv4=2305843009213693946;
int n;
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=(__int128)ans*a%mod;
a=(__int128)a*a%mod;
b>>=1;
}
return ans;
}
int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=((__int128)10*x+ch-'0')%(mod-1),ch=getchar();
return x;
}
signed main(){
n=read();
int ans=((__int128)qpow(2+sqrt2,n+1)*inv4%mod+(__int128)qpow(2-sqrt2+mod,n+1)*inv4%mod-qpow(2,n)+mod)%mod;
printf("%lld\n",ans);
return 0;
}
末日狂欢
考虑一个缩边的过程:每次找一个度数 \(2\) 的点删掉,然后如果相邻两个节点没边就连上。那么最后这张图会被缩成一条边。
设 \(dp_{i,0/1,0/1}\) 为两个端点是否在最大独立集中,属于边 \(i\) 的点的最大独立集大小。转移时找到要删的点,合并两边的边的答案,枚举是这个点在独立集里还是两边在独立集里。然后连边,如果没边直接连,如果有边要先把这条边拿出来处理贡献删掉,然后再连回去。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
#include <cstring>
#include <queue>
using namespace std;
int n,m,tmp[2][2];
struct node{
int v,dp[2][2];
node(int s=0):v(s){memset(dp,-1,sizeof(dp));}
bool operator<(const node&s)const{
if(v!=s.v)return v<s.v;
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
if(dp[i][j]!=s.dp[i][j])return dp[i][j]<s.dp[i][j];
}
}
return false;
}
}edge;
set<node>st[50010];
queue<int>q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
edge.v=v;edge.dp[0][0]=0;edge.dp[0][1]=edge.dp[1][0]=1;edge.dp[1][1]=-0x3f3f3f3f;
st[u].insert(edge);
edge.v=u;st[v].insert(edge);
}
for(int i=1;i<=n;i++)if(st[i].size()==2)q.push(i);
for(int t=1;t<=n-2;t++){
int x=q.front();q.pop();
node l=*st[x].begin();st[x].erase(l);
node r=*st[x].begin();st[x].erase(r);
edge.v=x;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=l.dp[j][i];
st[l.v].erase(edge);
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=r.dp[j][i];
st[r.v].erase(edge);
for(int i=0;i<2;i++)for(int j=0;j<2;j++){
tmp[i][j]=max(l.dp[0][i]+r.dp[0][j],l.dp[1][i]+r.dp[1][j]-1);
}
set<node>::iterator it=st[l.v].lower_bound(node(r.v));
if(it!=st[l.v].end()&&it->v==r.v){
for(int i=0;i<2;i++)for(int j=0;j<2;j++){
tmp[i][j]+=it->dp[i][j]-(i+j);
}
st[l.v].erase(it);
it=st[r.v].lower_bound(node(l.v));
st[r.v].erase(it);
edge.v=r.v;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=tmp[i][j];
st[l.v].insert(edge);
edge.v=l.v;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=tmp[j][i];
st[r.v].insert(edge);
if(st[l.v].size()==2)q.push(l.v);
if(st[r.v].size()==2)q.push(r.v);
}
else{
edge.v=r.v;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=tmp[i][j];
st[l.v].insert(edge);
edge.v=l.v;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)edge.dp[i][j]=tmp[j][i];
st[r.v].insert(edge);
}
}
int l=q.front();
edge=*st[l].begin();
int ans=0;
for(int i=0;i<2;i++)for(int j=0;j<2;j++)ans=max(ans,edge.dp[i][j]);
printf("%d\n",ans);
return 0;
}
游戏
原题 AT Xmas Contest 2021 C。
双射题怎么看都看上去不能做。那不懂就问双射题该怎么做。
首先考虑没有 ?
怎么做。显然倒着变成删字符。一个神奇的题意转化是钦定 \(0\) 段先删结尾,\(1\) 段先删开头,每次删除在 \(01\) 中间空位填数,形成一个 \(1-n+1\) 的排列。那么此时就是把 \(0\) 看成 >
,\(1\) 看成 <
,做 loj#575。这是容易的。
然后加上 ?
。显然它代表没限制,那么按着拆成段,最后乘个多重组合数就行了。
多项式全家桶就不挂了。
int n,cnt[500010];
char s[500010],t[500010];
poly dp;
void solve(int l,int r){
if(l==r){
if(!l)dp[l]=1;
else dp[l]=cnt[l-1]&1?(mod-dp[l]):dp[l];
return;
}
int mid=(l+r)>>1;
solve(l,mid);
poly a(r-l+2),b(r-l+2);
for(int i=l;i<=mid;i++){
if(t[i]=='>')a[i-l]=(cnt[i]&1?mod-dp[i]:dp[i]);
}
for(int i=l;i<=r;i++)b[i-l]=Inv[i-l];
a=a*b;
for(int i=mid+1;i<=r;i++)dp[i]=add(dp[i],a[i-l]);
solve(mid+1,r);
}
int getans(){
int n=strlen(t+1)+1;
dp.resize(n+1);cnt[0]=0;
for(int i=0;i<=n;i++)dp[i]=0;
for(int i=1;i<=n;i++)cnt[i]=cnt[i-1]+(t[i]=='>');t[0]='>';
solve(0,n);
return dp[n];
}
int main(){
scanf("%d%s",&n,s+1);n++;init(n+1<<1);
for(int i=1;i<n;i++){
if(s[i]=='0')s[i]='<';
if(s[i]=='1')s[i]='>';
}
int ans=1;
for(int l=1,r;l<n;l=r+1){
r=l;
if(s[l]=='?')continue;
while(r+1<n&&s[r+1]!='?')r++;
for(int i=l;i<=r;i++)t[i-l+1]=s[i];t[r-l+2]=0;
ans=1ll*ans*getans()%mod;
}
ans=1ll*ans*jc[n]%mod;
printf("%d\n",ans);
return 0;
}
快踩