2020 CCPC Wannafly Winter Camp Day6 (部分題解)

感謝敦哥敦哥友好籤到題大放送,A題數目最多的一場

7-1 6A. Convolution

i=1nj=1n2aiaj\sum_{i=1}^n\sum_{j=1}^n2^{a_ia_j}這個式子看上去很像卷積,如果是卷積的話直接NTT就做完了,所以我們想辦法康康能不能化成卷積形式:
2aiaj=2(ai+aj)2ai2aj2=2ai22aj22(ai+aj)22^{a_ia_j}=\sqrt2^{(a_i+a_j)^2-a_i^2-a_j^2}=\sqrt2^{-a_i^2}*\sqrt2^{-a_j^2}*\sqrt2^{(a_i+a_j)^2}
這樣把就把aia_i對應的2ai2\sqrt2^{-a_i^2}aja_j對應的2aj2\sqrt2^{-a_j^2}這兩個的乘積與(ai+aj)(a_i+a_j)對應的2(ai+aj)2\sqrt2^{(a_i+a_j)^2}對應起來,相加的值相同的歸爲一類,這樣就轉換成卷積的形式了
代碼如下:

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
using namespace std;
#define LL long long int
using namespace std;
const ll hi = 116195171;
const int maxn = 1<<20,maxm = 100005,INF = 1000000000;
const int G = 3,P = (119 << 23) + 1;
const ll mod = (119<<23)+1;
int n,m,L,R[maxn];
int A[maxn],B[maxn];
ll qm(ll a, ll b){ll res = 1; while(b){if(b&1) res = res*a%mod; a = a*a%mod; b >>= 1; } return res; }
ll qpow(ll a,ll b){
    ll ans = 1;
    for (; b; b >>= 1,a = 1ll * a * a % P)
        if (b & 1) ans = 1ll * ans * a % P;
    return ans;
}
void NTT(int* a,int f){
    for (int i = 0; i < n; i++) if (i < R[i]) swap(a[i],a[R[i]]);
    for (int i = 1; i < n; i <<= 1){
        int gn = qpow(G,(P - 1) / (i << 1));
        for (int j = 0; j < n; j += (i << 1)){
            int g = 1;
            for (int k = 0; k < i; k++,g = 1ll * g * gn % P){
                int x = a[j + k],y = 1ll * g * a[j + k + i] % P;
                a[j + k] = (x + y) % P; a[j + k + i] = (x - y + P) % P;
            }
        }
    }
    if (f == 1) return;
    int nv = qpow(n,P - 2); reverse(a + 1,a + n);
    for (int i = 0; i < n; i++) a[i] = 1ll * a[i] * nv % P;
}
int a[maxm];
int main()
{
    scanf("%d", &n);
    ll invh = qpow(hi, mod-2);
    int len = 0;
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]); len = max(len , a[i]);
        ll t = qm(invh, (ll)a[i]*a[i]);
        A[a[i]] += t;
        A[a[i]] %= P;
    }

    len = 2*len+1; for (n = 1; n <= len; n <<= 1) L++;
    for (int i = 0; i < n; i++) R[i] = (R[i >> 1] >> 1) | ((i & 1) << (L - 1));

    NTT(A, 1);
    for (int i = 0; i < n; i++) A[i] = 1ll * A[i] * A[i] % P;
    NTT(A,-1);
    ll ans = 0;
    for(int i = 0; i < len; ++i){
        ans = (ans + A[i]*qm(hi, (ll)i*i))%mod;
    }
    cout<<ans<<endl;
}
/*
3
100000 5 1000
*/

7-3 6C. 酒館戰棋

模擬就完事了

#include<bits/stdc++.h>
using namespace std;

char s[1010];

int mxkill(int n, int a, int b, int c, int d){
    int res=0;
    for (int i=1;i<=n;++i){
        if (s[i]=='1'){
            if (c){
                c--;    res++;  continue;
            }
            if (d){
                d--;    c++;    continue;
            }
            if (a){
                a--;    res++;  continue;
            }
            if (b){
                b--;    a++;    continue;
            }
        }else{
            if (d){
                d--;    c++;    continue;
            }
            if (c){
                continue;
            }
            if (b){
                b--;    a++;    continue;
            }
        }
    }
    return res;
}

int mikill(int n, int a, int b, int c, int d){
    int res=0;
    for (int i=1;i<=n;++i){
        if (s[i]=='1'){
            if (d){
                d--;    c++;    continue;
            }
            if (c){
                c--;    res++;  continue;
            }
            if (b){
                b--;    a++;    continue;
            }
            if (a){
                a--;    res++;  continue;
            }
        }else{
            if (c){
                continue;
            }
            if (d){
                d--;    c++;    continue;
            }
            if (a){
                continue;
            }
            if (b){
                b--;    a++;    continue;
            }
        }
    }
    return res;
}

void sol(){
    int n;
    scanf("%d",&n);
    int a,b,c,d;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    scanf("%s",s+1);
    printf("%d %d\n",mxkill(n,a,b,c,d),mikill(n,a,b,c,d));
    return ;
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        sol();
    }
    return 0;
}

7-6 6F. 圖與三角形

枚舉每條邊,這條邊的兩端點連的與該邊不同色邊數量/4(非整除)爲對答案的貢獻。

#include<bits/stdc++.h>
using namespace std;
long long A,B,C,P,D,ans;
vector<pair<pair<int,int>,int> > E;
long long n, sum;
long long du[2][5050];
void sol(){
    pair<int,int> edge;
    ans=0;
    int op,u,v;
    int lim=E.size();
    for (int i=0;i<lim;++i){
        edge=E[i].first;    op=E[i].second;
        u=edge.first;   v=edge.second;
        ans+=du[op^1][u];   ans+=du[op^1][v];
    }
    return ;
}


int main(){
    scanf("%lld",&n);
    sum = n*(n-1)*(n-2);    sum/=3;  sum/=2;
    scanf("%lld%lld%lld%lld%lld",&A,&B,&C,&P,&D);
    long long tmp;
    for (int i=1;i<=n;++i){
        for (int j=i+1;j<=n;++j){
            tmp=(A*(i+j)*(i+j)+B*(j-i)*(j-i)+C)%P;
            if (tmp>D){
                du[1][i]++; du[1][j]++;
                E.push_back(make_pair(make_pair(i,j),1));
            }else{
                du[0][i]++; du[0][j]++;
                E.push_back(make_pair(make_pair(i,j),0));
            }
        }
    }
    sol();
    cout<<sum-ans/4<<"\n";
    return 0;
}

7-7 6G. 單調棧

貪心,見代碼(隊友寫的)

#include<bits/stdc++.h>
#define LL long long
#define inf 0x3f3f3f3f
#define test freopen("in","r",stdin);freopen("out","w",stdout);
#define lson rt<<1
#define rson rt<<1|1
#define PII pair<int,int>
using namespace std;
const int maxn=105;
int n,a[maxn],ans[maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(ans,-1,sizeof(ans));
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",a+i);
        int nc=0,p=1;
        while(nc!=n)
        {
            for(int i=n;i;--i)
            {
                if(a[i]==p) ans[i]=++nc;
            }
            for(int i=1;i<=n;++i)
            {
                if(a[i]==-1) {a[i]=p,ans[i]=++nc;break;}
                else if(a[i]==p) break;
            }
            ++p;
        }
        printf("%d",ans[1]);
        for(int i=2;i<=n;++i) printf(" %d",ans[i]);
        puts("");
    }
    return 0;
}

7-9 6I. 變大!

最後的結果肯定是把序列分成若干個值相等的塊。那麼我們可以考慮對於一個長度爲n的塊,它最後的成爲值相等的塊需要幾步:
如果n是奇數,那麼它的最大值兩邊數目要麼都是奇數,要麼都是偶數,如果都是偶數,那麼除了第一次覆蓋了3個位置,剩餘每次覆蓋兩個位置,一共n/2次操作。如果都是奇數,那麼以最大值所 位置爲中心覆蓋一次,然後兩邊剩餘就都是偶數了。操作次數也是n/2.
n爲偶數也一樣分析出操作次數爲n/2
所以用dp(i,j)dp(i,j)表示弄完前i個數字,操作了j次得到的最大值,然後枚舉最後一個塊的長度去轉移就可,複雜度O(n3)O(n^3)

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 55;
int n;
int a[maxn];
int dp[maxn][maxn];
int main()
{
    int T; cin>>T;
    while(T--){
        scanf("%d", &n);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        for(int i = 1; i <= n; ++i){
            int mx = a[i];
            for(int j = i-1; j >= 0; --j){
                int c = (i-j)/2;
                for(int k = 0; k +c <= i; ++k){
                    dp[i][k+c] = max(dp[i][k+c], dp[j][k] + mx*(i-j));
                }
                mx = max(mx, a[j]);
            }
        }
        for(int k = 1; k <= n; ++k){
            printf("%d", dp[n][k]);
            if(k != n) printf(" ");
            else printf("\n");
        }
    }
}

7-10 6J. K重排列

當k等於1的時候,答案爲N!
否則因爲每個點都可以回來,所以一定構成若干個環,並且這些環的長度都是k的約數。那麼考慮dp加環
dp(i,j)dp(i,j)表示考慮到第i個約數,加入了j個點的方案數,然後轉移。
現在要計算從n個點裏面選出t個k個點構成t個環的方案數。首先,n個點選t個k個點的無序集合的方案數爲F(n,k,t)=i=0tC(nik,k)t!=n!t!(k!)t(ntk)!F(n,k,t)=\frac{\sum_{i=0}^tC(n-ik,k)}{t!}=\frac{n!}{t!*(k!)^t*(n-tk)!}
每個kk點集合可以構成(k1)!(k-1)!個不同圓環。所以再乘上(k1)t(k-1)^t就是從n個點裏面選出t個k個點構成t個環的方案數。
然後根據這個dp就可以了

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 55;
const ll mod = 998244353;
ll fac[maxn], ifac[maxn], inv[maxn];
ll qm(ll a, ll b){
    ll res = 1;
    while(b){
        if(b&1) res = res*a%mod;
        a = a*a%mod;
        b >>= 1;
    }return res;
}
ll dp[maxn][maxn];
ll cal(int n, int k, int t){
    if(k == 0) return 1;
    ll res = fac[n]*ifac[t]%mod*qm(inv[k], t)%mod*ifac[n-t*k]%mod;
    return res;
}
int main()
{
    int n; ll k;
    fac[0] = ifac[0] = 1;
    for(int i = 1; i < maxn; ++i){
        fac[i] = fac[i-1]*i%mod;
        ifac[i] = qm(fac[i], mod-2);
        inv[i] = qm(i, mod-2);
    }
    int T; cin>>T;
    while(T--){
        scanf("%d%lld", &n, &k);
        if(n == 1){
            printf("1\n");
        }else if(k == 1){
            printf("%lld\n", fac[n]);
        }else{
            //k--;
            memset(dp, 0, sizeof dp);
            dp[0][0] = 1;
            int cur = 0;
            for(int i = 1; i <= n; ++i){
                if(k%i) continue;
                for(int j = 0; j <= n; ++j){
                    if(!dp[cur][j]) continue;
                   // cout<<"i:"<<i<<" j:"<<j<<" "<<dp[cur][j]<<endl;
                    for(int u = 0; j + u <= n; u += i){
                        //cout<<"u/i:"<<u/i<<endl;
                        dp[cur+1][j+u] += dp[cur][j]*cal(n-j,i, u/i)%mod;
                        dp[cur+1][j+u] %= mod;
                       // cout<<"res:"<<dp[cur][j]*cal(n-j,i, u/i)<<endl;
                    }
                }
                cur++;
            }
            printf("%lld\n", dp[cur][n]);
        }
    }
}

4個快樂簽到題就不贅述了~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章