8.29 正睿 NOIP 十连测 Day1 题解

T1

推式子:

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^p\phi(i^j)=\sum\limits_{i=2}^n\frac{\phi(i)\times (i^p-1)}{i-1}+p \]

所以我们筛一下 \(f(x)=\phi(x)\times x^p\) 和 \(\phi(x)\) 还有线性逆元就做完了。注意快速幂只对素数做就可以。所以复杂度正确。

代码:

#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 10000100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e9+7;

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 n,p;

inline ll ksm(ll a,ll b,ll mod){
    ll res=1;while(b){if(b&1) res=res*a%mod;a=a*a%mod;b>>=1;}return res;
}

ll f[N],phi[N],Pow[N],inv[N],ans;
int Prime[N],tail;
bool NotPrime[N];

inline void PreWork(int n){
    NotPrime[1]=1;f[1]=1;phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!NotPrime[i]){Prime[++tail]=i;phi[i]=i-1;Pow[i]=ksm(i,p,mod);f[i]=phi[i]*Pow[i]%mod;}
        for(int j=1;j<=tail&&i*Prime[j]<=n;j++){
            int k=i*Prime[j];NotPrime[k]=1;
            if(i%Prime[j]==0){
                phi[k]=phi[i]*Prime[j]%mod;f[k]=f[i]*Prime[j]%mod*Pow[Prime[j]]%mod;break;
            }
            else{
                phi[k]=phi[i]*phi[Prime[j]]%mod;f[k]=f[i]*f[Prime[j]]%mod;
            }
        }
    }
    // for(int i=1;i<=n;i++) printf("%lld ",f[i]);puts("");
    // for(int i=1;i<=n;i++) printf("%lld ",phi[i]);puts("");
    inv[1]=1;
    for(int i=2;i<=n;i++){inv[i]=(mod-mod/i)*inv[mod%i]%mod;}
    // for(int i=1;i<=n;i++) printf("%lld ",inv[i]);puts("");
}

int main(){
    freopen("my.in","r",stdin);
    freopen("my.out","w",stdout);
    read(n);read(p);PreWork(n);
    for(int i=2;i<=n;i++){
        ans=(ans+inv[i-1]*(f[i]-phi[i])%mod)%mod;
    }
    ans=(ans+p)%mod;
    printf("%lld\n",(ans%mod+mod)%mod);
    return 0;
}

T2

大根堆考虑从大到小依次考虑每一个数。设当前数为 \(x\),可以放置的位置个数为 \(res\),那么如果 \(x\) 没有钦定为叶子,放置完 \(x\) 后会 \(res:=res+1\),否则 \(res:=res-1\),随时维护 \(res\),然后乘起来就可以。

代码:

#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 1000010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e9+7;

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;
}

bool vis[N];
int n,S,s[N];
ll ans=1,res=1;

int main(){
    read(n);read(S);
    for(int i=1;i<=S;i++){
        read(s[i]);vis[s[i]]=1;
    }
    if(n==1){puts("1");return 0;}
    if(vis[n]){puts("0");return 0;}
    for(int i=n;i>=1;i--){
        ans=ans*res%mod;
        if(vis[i]) res--;
        else res++;
    }
    printf("%lld\n",ans);
    return 0;
}

T3

用的 zyc 大佬的思路,这里说的详细一点。

我们考虑这个序列字典序最大是多少,假设 \(n=4\),那么这个序列是:

\[\texttt{4,4,4,4,4,4,3,2,2,1,1,1} \]

我们考虑这个序列是怎么形成的,一开始有 \(n\) 个空点,然后相邻连边,就可以使 scc 个数不变。加的不能加为止,不难发现,每多连一条边,就可以有 \(x\) 条边使得 scc 个数为 \(n-x\) 不变。这个自己画一下图就能论证。

接下来我们考虑如果已知一个前缀 scc 序列,如何看下一个值最小能填多少?我们不难发现,这个数是 \(n-k+1\) 其中 \(n\) 是序列长度,\(k\) 是序列中不同的数的个数。这个很好论证,用构造的方法就可以证明。

至于上界,这个就是上一位和这一位的 \(down\) 值做 \(min\) 就可以。

上下界固定后,暴力就好想了,dp 也很自然。

设 \(f_{i,j,k}\) 表示序列长度为 \(i\),最后一个数为 \(j\),有 \(k\) 个不同的数的方案数。

我们考虑有哪些状态可以转移到 \(f_{i,j,k}\)。我们考虑如果这个序列中 \(j\) 不止一个,那么就可以从 \(f_{i-1,j,k}\) 中转移。

否则,我们枚举第 \(i-1\) 个填了什么,然后转移,不难发现这个东西是 \(f_{i-1,j',k-1}\) 其中 \(j'>j\),因为不合法的状态我们已经设置为 \(0\),所以可以放心转移。用前缀和优化,可以做到 \(O(n^4)\)。

代码:

#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 101
#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;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

int t;
int f[N*N][N][N],sum[N][N],mod,ans[N*N],down[N*N],n;

inline void GetDown(int n){
    for(int i=1;i<=n*(n-1)/2;i++) down[i]=n;
    int len=n*(n-1)/2;
    for(int i=1;i<=n-1;i++){
        for(int j=1;j<=i;j++) down[++len]=n-i;
    }
    // for(int i=1;i<=n*(n-1);i++) printf("%d ",down[i]);
}

inline int Del(int x){
    return x>=mod?x-mod:x;
}

inline void Clear(){
    memset(f,0,sizeof(f));memset(sum,0,sizeof(sum));memset(down,0,sizeof(down));
    memset(ans,0,sizeof(ans));
}

#define Sum(l,r) (sum[r][k-1]-sum[l-1][k-1])
inline void Solve(int n){
    // f[i][j][k] 表示序列长度为i,序列最后一个数为j,前面一共有k个不同的数。
    f[1][n][1]=1;printf("1 ");
    for(int i=2;i<=n*(n-1);i++){
        for(int j=1;j<=n&&j<=i;j++){
            sum[1][j]=f[i-1][1][j];
            for(int k=2;k<=down[i-1];k++)
                sum[k][j]=Del(sum[k-1][j]+f[i-1][k][j]);
        }//预处理前缀和,sum[k][j]=f[i-1][1...k][j]
        for(int j=1;j<=down[i];j++){
            for(int k=1;k<=n&&k<=i&&k<=(n-j+1);k++){
                //枚举状态 f[i][j][k]
                int now=Max(j,n-(i-k+1));
                //计算下界
                if(now>j) continue;
                //如果比 j 大,那么状态不合法。
                f[i][j][k]=(Sum(now+1,down[i-1])+mod)%mod;
                f[i][j][k]=Del(f[i][j][k]+f[i-1][j][k]);
                ans[i]=Del(ans[i]+f[i][j][k]);
            }
        }
        printf("%d ",ans[i]);
    }
    puts("");
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(t);
    while(t--){
        Clear();
        read(n);read(mod);
        GetDown(n);Solve(n);
    }
}

T4

首先,设 \(x\) 为所有数的异或值,如果 \(x=0\) ,那么就是平局,否则,找到最高位的 \(1\) ,这一位上 \(1\) 的个数为奇数,表示 Alice 和 Bob 一定不同,所以题目简化成给定长度为 \(n\) 的 \(01\) 串,来做那些操作判断是否先手必胜。

我们考虑如果 \(n\) 是偶数,那么先手必胜,理由是我们对整个串按照下标是否为奇数进行分组,我们发现因为 \(n\) 是偶数,所以先手可以选所有偶数位置或所有奇数位置,因为 \(1\) 的个数为奇数,所以一定有一种方案使得 \(1\) 的个数为奇数。

如果 \(n\) 是奇数,我们考虑如果两端都是 \(0\),那么先手必败,这是因为无论先手选哪个,后手面对的都是上面的先手必胜态,所以先手必败。

考虑两端至少有一个 \(1\)。可以肯定的是先手必须选 \(1\),剩下的我们来看怎么选先手能胜。不难发现,需要满足两个要求:

  • 首先,无论接下来后手选什么,先手都必须选和后手一样的。这是因为如果选的不一样,这就转化成了上面的必胜态,所以先手必输。
  • 并且,剩下的 \(1\) 的个数必须满足是 \(4\) 的倍数。否则,即使满足上面的要求,选到最后先手手中的数字会变成 \(0\) 。

判断即可。

需要注意的是,如果两端都是 \(1\) ,那么需要判断两段,数据在程序末尾:

#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 100010
#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 all,a[N],n,cnt,t;

inline void Init(){
    read(n);cnt=0;all=0;
    for(int i=1;i<=n;i++){
        read(a[i]);all^=a[i];
    }
}

inline bool Check(ll l, ll r)
{
	while(l < r && a[l] == a[r]) l++,r--;
    for(int i=l;i<=r;i+=2){
		if(a[i]!=a[i+1]) return 0;
	}
	return 1;
}

inline void Solve(){
    int w=-1;
    for(int i=30;i>=0;i--)
        if(((all>>i)&1)==1){w=i;break;}
    if(w==-1){puts("Draw");return;}
    for(int i=1;i<=n;i++){
        a[i]=(a[i]>>w)&1;if(a[i]) cnt++;
    }
    if((n&1)==0){puts("Alice");return;}
    if(a[1]==a[n]&&a[1]==0){puts("Bob");return;}
    cnt--;if(cnt%4!=0){puts("Bob");return;}
    if((a[n]&&Check(1,n-1))||(a[1]&&Check(2,n))){puts("Alice");return;}
    puts("Bob");
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(t);
    while(t--){
        Init();Solve();
    }
    return 0;
}
/*
1
19
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 
*/
上一篇:C语言:分支和循环语句(2)


下一篇:iOS 导航栏不可点击