冲刺国赛模拟 12

写题

原题 P4967 黑暗打击。签到题,而且看着就很原。

考虑直接做,不会做。观察得到一个分形是两个上个分形正着加上两个上个分形横过来。考虑把希尔伯特曲线横过来,算它能淹没多少。设第 \(n\) 个正着是 \(a_n\),横着是 \(b_n\)。那么我们可以递推了。

  1. 对于正着:两个正着 + 两个横着 + 中间的三列:\(a_n=2a_{n-1}+2b_{n-1}+3\times 2^{n-1}-2\)
  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;
}
posted @ 2023-06-07 15:18  gtm1514  阅读(15)  评论(0编辑  收藏  举报