zoukankan      html  css  js  c++  java
  • 9.5正睿NOIP十连测day2

    总体来说难度还行,不如昨天的 csp 恶心,还是要注意细节,因为两个字符挂了 70 分。

    T1

    容易发现这个东西如果最后胜者的出的东西确定了,那么整个序列其实就确定了,这个可以预处理出来,所以我们考虑使这个序列字典序最小。

    我们可以考虑用归并排序之类的方法,复杂度是 (O(nlog n)),其中 (n) 是序列长度。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 70010
    #define M number
    using namespace std;
    // #define fre
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int t,r,p,s,a[3][20][N],all,cnt[3][3];
    vector<int> v;
    string S[4];
    
    inline void Init(){
        v.clear();for(int j=0;j<=3;j++) S[j].clear();
        read(r);read(p);read(s);all=r+p+s;
        memset(cnt,0,sizeof(cnt));
    }
    
    inline void PreWork(){
        a[0][0][1]=0;a[1][0][1]=1;a[2][0][1]=2;
        for(int i=0;i<=14;i++){
            for(int j=1;j<=(1<<i);j++){
                for(int k=0;k<=2;k++){
                    if(a[k][i][j]==0){
                        a[k][i+1][(j<<1)-1]=0;
                        a[k][i+1][(j<<1)]=1;
                    }
                    else if(a[k][i][j]==1){
                        a[k][i+1][(j<<1)-1]=2;
                        a[k][i+1][(j<<1)]=1;
                    }
                    else if(a[k][i][j]==2){
                        a[k][i+1][(j<<1)-1]=0;
                        a[k][i+1][(j<<1)]=2;
                    }
                }
            }
        }
    }
    
    inline void Solve(int k,int n,int id){
        for(int i=1;i<=n;i++){
            int len=(1<<i);
            for(int j=1;j<=(1<<n);j+=len){
                int p=len/2;bool op=1;
                for(int q=1;q<=p;q++){
                    if(a[k][n][j+q-1]!=a[k][n][j+q-1+p]){
                        if(a[k][n][j+q-1]<a[k][n][j+q-1+p]) op=1;
                        else op=0;
                        break;
                    }
                }
                if(!op){
                    for(int q=1;q<=p;q++){
                        swap(a[k][n][j+q-1],a[k][n][j+q-1+p]);
                    }
                }
            }
        }
    
        for(int j=1;j<=(1<<n);j++){
            if(a[k][n][j]==0) S[id]+=(string)"P";
            else if(a[k][n][j]==1) S[id]+=(string)"R";
            else if(a[k][n][j]==2) S[id]+=(string)"S";
        }
    }
    
    int main(){
        #ifdef fre
            freopen("my.in","r",stdin);
            freopen("my.out","w",stdout);
        #endif
        read(t);PreWork();
        while(t--){
            Init();int n=-1;while(all){n++;all>>=1;}
            for(int j=1;j<=(1<<n);j++){
                for(int k=0;k<=2;k++){
                    cnt[k][a[k][n][j]]++;
                }
            }
            bool op=0;
            for(int k=0;k<=2;k++){
                if(r==cnt[k][1]&&p==cnt[k][0]&&s==cnt[k][2]){op=1;v.push_back(k);}
            }
            if(!op){printf("-1
    ");continue;}
            for(int j=0;j<v.size();j++){
                Solve(v[j],n,j);
            }
            string ans=S[0];
            for(int j=0;j<v.size();j++) ans=min(ans,S[j]);
            cout<<ans<<'
    ';
        }
        return 0;
    }
    

    永远不要相信自己的贪心策略,最好还是打表验证一下。

    T2

    考场上握手先想到的是 DP,但空间时间开销过大。容易发现我们可以构造图来帮助我们做题。其中每一个节点都是一个余数,我们通过往后增加可行的数,在节点之间连边。如果节点 (0) 与我们的起始节点有同路,说明有解。至于找字典序最小的解,我们只需要让 bfs 遍历的顺序优先选择字典序小的即可,这样先遍历到的就是字典序最小的。

    注意因为余数可能不超过 (9),所以在从总结点向初始结点连边的时候注意需要取模。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 1000100
    #define M number
    using namespace std;
    // #define fre
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    struct edge{
        int to,next,w;
        inline void Intt(int to_,int ne_,int w_){
            to=to_;next=ne_;w=w_;
        }
    };
    edge li[N*9];
    int head[N],tail;
    
    inline void Add(int from,int to,int w){
        li[++tail].Intt(to,head[from],w);
        head[from]=tail;
    }
    
    int a,k,pre[N],epre[N],ans[N<<1],anstail;
    bool wei[N],vis[N];
    vector<int> v;
    queue<int> q;
    
    int main(){
        #ifdef fre
            freopen("my.in","r",stdin);
            freopen("my.out","w",stdout);
        #endif
        read(a);read(k);
        for(int i=1;i<=k;i++){
            int x;read(x);wei[x]=1;
        }
        for(int i=0;i<=9;i++) if(!wei[i]) v.push_back(i);
        for(int i=1;i<=a-1;i++){
            for(int j=v.size()-1;j>=0;j--){
                int now=(i*10+v[j])%a;
                Add(i,now,v[j]);
            }
        }
        for(int j=v.size()-1;j>=0;j--) if(v[j]!=0) Add(a,v[j]%a,v[j]);
        q.push(a);
        vis[a]=1;
        while(q.size()){
            int top=q.front();q.pop();
            for(int x=head[top];x;x=li[x].next){
                int to=li[x].to,w=li[x].w;
                if(vis[to]) continue;
                vis[to]=1;pre[to]=top;epre[to]=w;
                q.push(to);
            }
        }
        if(!vis[0]){printf("-1
    ");return 0;}
        int now=0;
        while(now!=a){
            ans[++anstail]=epre[now];
            now=pre[now];
        }
        for(int i=anstail;i>=1;i--) printf("%d",ans[i]);
        return 0;
    }
    

    T3

    其实有一个结论是最后的答案一定可以被分为若干连通块,其中每一个连通块一定都是一个二部图,即每一个节点向其所有相对的节点连边。我们首先考虑证明这个东西。

    (n) 表示左部点右部点节点个数,当 (n)(1,2) 的时候,很明显结论成立,现在我们假设 (n-1) 时结论成立,考虑 (n)。假设极端情况,目前第 (n) 个左部点我们设为 (l_n),第 (n) 个右部点我们设为 (r_n)。因为其余 (n-1) 个左部点和右部点形成了一个二部图。所以点与点之间等价。我们不妨设 (l_n)(r_1,r_2,...r_{n-2},) 都有连边,设 (r_n)(l_1,l_2,...l_{n-2}) 都有连边。

    • 第一种情况是 (l_n)(r_n) 之间有边,而 (l_n,r_{n-1}) 以及 (l_{n-1},r_n) 之间没有边。

      这个时候我们选 ((l_1,r_2),(l_2,r_3),...,(l_{n-2},r_{n-1}),(l_n,r_1)),这个时候你发现 (r_n) 匹配不上了。

    • 第二种情况是 (l_n,r_{n-1})(r_n,l_{n-1}) 之间有边,而 (l_n,r_n) 之间没边。

      这个时候我们选 ((l_1,r_1),(l_2,r_2),...(l_{n-1},r_{n-1})) 就可以使 (l_n,r_n) 匹配不上。

    剩下的所有情况都是上面这两种情况的子图,易知边越少越不可能匹配。

    其实实战中画一下 (n=2,n=3),知道可以用归纳法,这个结论就算证明完了。

    或者压根不证明,自信相信自己的结论,卡不掉就是对的。

    然后我们接下来的工作就是划分连通块,因为 (n) 的个数,所有原图上的连通块个数不会很多,我们把只有一个点的连通块拿出来,剩下的我们直接大力搜索合并连通块,在加上一些减枝,跑的不知道比谢老板的状压快多少。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 60
    #define M number
    using namespace std;
    // #define fre
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    template<typename T> inline T Min(T a,T b){return a<b?a:b;}
    template<typename T> inline T Max(T a,T b){return a<b?b:a;}
    
    int sizel[N],sizer[N];
    
    struct DSU{
        int fa[N];
        inline void Init(int n){for(int i=1;i<=n;i++) fa[i]=i;}
        inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
        inline void Merge(int a,int b){
            int faa=Find(a),fab=Find(b);fa[faa]=fab;
            if(faa==fab) return;
            sizel[fab]+=sizel[faa];sizer[fab]+=sizer[faa];
        }
    }dsu;
    
    int t,n,l,r,ans,edgecnt,sum,T;
    char s[N];
    
    bool vis[N];
    typedef pair<int,int> P;
    vector<P> v;
    
    inline void Clear(){
        v.clear();for(int i=0;i<=(n<<1);i++) vis[i]=0;
        for(int i=1;i<=(n<<1);i++) sizel[i]=sizer[i]=0;
        edgecnt=0;sum=0;l=r=0;
        // l=r=0;ans=0;edgecnt=0;sum=0;memset(vis,0,sizeof(vis));
        // n=0;v.clear();memset(sizel,0,sizeof(sizel));memset(sizer,0,sizeof(sizer));
        // memset(dsu.fa,0,sizeof(dsu.fa));
    }
    
    bool op=0;
    
    inline void Init(){
        read(n);dsu.Init(n<<1);
        for(int i=1;i<=(n<<1);i++){sizel[i]=(i<=n);sizer[i]=(i>n);}
        for(int i=1;i<=n;i++){
            scanf("%s",s);int len=strlen(s);
            for(int j=0;j<=len-1;j++){
                if(s[j]=='1'){dsu.Merge(i,j+1+n);edgecnt++;}
            }
            // if(op&&!t&&T==5&&n==8) cout<<s<<endl;
        }
        for(int i=1;i<=(n<<1);i++){
            if(dsu.fa[i]!=i) continue;
            if(sizel[i]==sizer[i]){sum+=sizel[i]*sizel[i];continue;}
            if(sizer[i]==0) l++;
            if(sizel[i]==0) r++;
            if(sizel[i]&&sizer[i]) v.push_back(make_pair(sizel[i],sizer[i]));
        }
        ans=n*n;
    }
    
    inline void Solve(int k,int l,int r,int all,int delta,int last){
        // if(k==v.size()+1){ans=Min(ans,sum+l);return;}
        if(last<0){
            if(k==v.size()+1){ans=Min(ans,sum+l);return;}
        }
        if(sum+Max(l,r)>=ans) return;
        if(last>=0){
            sum=sum+(all+Max(delta,0))*(all+Max(delta,0));
            if(delta>=0&&delta<=l) Solve(k,l-delta,r,0,0,-1);
            else if(delta<0&&delta+r>=0) Solve(k,l,r+delta,0,0,-1);
            sum=sum-(all+Max(delta,0))*(all+Max(delta,0));
        }
        for(int i=last+1;i<=v.size()-1;i++){
            if(vis[i]) continue;
            vis[i]=1;
            Solve(k+1,l,r,all+v[i].first,delta-v[i].first+v[i].second,i);
            vis[i]=0;
        }
    }
    
    int main(){
        #ifdef fre
            freopen("my.in","r",stdin);
            freopen("my.out","w",stdout);
        #endif
        read(t);T=t;
        while(t--){
            Init();
            Solve(1,l,r,0,0,-1);
            printf("%d
    ",ans-edgecnt);
            if(ans-edgecnt==10) op=1;
            else op=0;
            Clear();
        }
    }
    

    T4

    首先我们考虑如果只有一个这样双层的图,方案数很好计算,设 (g_{i,j}) 表示第一层剩下 (i) 个,第二层剩下 (j) 个的方案数是多少,然后转移方程就是 (g_{i,j}=g_{i-1,j}+g_{i,j-1}),当然 (i>j) 的状态是没有意义的,注意要弄成 (0)

    接下来我们回到原图上,时间复杂度要求 (n^2),我们仍然考虑 dp,令 (f_{i,j}) 表示第一层选了 (i) 个,第二层选了 (j) 个的方案数。

    如果当前 (i=j),那么因为要保证状态的合法性,我们只能选上面的,在选了上面的之前,因为 (j) 控制着一组图的第一层,我们需要枚举这一组图的第一层选了多少个,然后我们选一个上面的,再然后这一组图就会完全独立,我们只需要考虑把他们的拓扑序,即这个长度为 (2n) 的序列放在哪里,这是一个组合数,然后我们在乘上这个序列拓扑序方案数。

    否则,如果我们拿上层的一个,我们直接从 (f_{i,j}) 转移到 (f_{i+1,j}) 即可,如果我们拿下层的一个,如果 (j+1=i) ,那么我们依然还是直接拿即可。

    如果 (j+1<i),那么这个时候会有一组完全独立,我们还是直接把他们放入当前的拓扑序中,用方案数乘上一个组合数。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 3010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int fac[N*N*2],inv[N*N*2],f[N][N],g[N][N],mod,n;
    
    inline int C(int n,int m){
        if(m>n) return 0;
        return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
    }
    
    inline void PreWork(int n){
        fac[1]=1;inv[1]=1;fac[0]=inv[0]=1;
        for(int i=2;i<=n;i++){
            fac[i]=1ll*fac[i-1]*i%mod;
            inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
        }
        for(int i=2;i<=n;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
    }
    
    int main(){
        read(n);read(mod);PreWork(n*n*2);
        for(int i=0;i<=n;i++) g[0][i]=1;
        for(int i=1;i<=n;i++)
            for(int j=i;j<=n;j++) g[i][j]=(g[i-1][j]+g[i][j-1])%mod;
        f[1][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=i;j++){
                if(j==i){
                    if(j==n) break;
                    for(int k=0;k<=n;k++){
                        int now=i+1+j+k+(i-1)*2*n;
                        f[i+1][j]=(f[i+1][j]+1ll*g[n-k][n]*C(2*n*n-now,2*n-k)%mod*f[i][j]%mod)%mod;
                    }continue;
                }
                if(i<n) (f[i+1][j]+=f[i][j])%=mod;
                if(i==j+1) (f[i][j+1]+=f[i][j])%=mod;
                else{
                    int now=i+j+1+j*2*n;
                    f[i][j+1]=(f[i][j+1]+1ll*g[n][n]*C(2*n*n-now,2*n)%mod*f[i][j]%mod)%mod;
                }
            }
        printf("%d
    ",f[n][n]);
        return 0;
    }
    
  • 相关阅读:
    Sublime_text3怎么运行php代码
    (转)解决png图片在IE6下不透明的方法
    CSS+JS下拉菜单和纯CSS下拉菜单
    笔记本开了WIFI之后只能上QQ,上不了网页的解决方法
    常见的浏览器兼容问题
    学习正则表达式
    (转)各种排序比较 直观动画
    find job
    mongodb 索引,全文索引与唯一索引
    再谈闭包,几个例子分析
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15238916.html
Copyright © 2011-2022 走看看