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
*/