test20180830
所有试题限制均为128MB,1Sec
总分100(•́へ•́╬)。
试题一
A题
问题描述:
Bob 有 n 个士兵,他们排成一列按照从左到右编号为 1 到 n,每个士兵都有自己的 IQ 值,Bob 喜欢有序的东西,他想要让这些士兵按照 IQ 的大小从小到大排序。于是 Bob 决定进行m 次操作,每次选一个士兵,让他开始整理部队。当士兵被选中的时候,士兵会向后的士兵(编号大于该士兵)发出指令。但是因为 IQ 的原因,只有 IQ 低于该士兵的士兵才会听从指令。那么这些士兵会按照 IQ 从小到大顺序重新排列在这些士兵之前的位置上,并重新编号。那么 Bob 想知道,当他选出一些士兵之后,总体士兵 IQ 的逆序对有多少个。
输入:
第一行包含三个整数 n,m,表示士兵的个数和 Bob 操作的次数。
接下来 n 个数字,\(a_1,a_2 \dots a_n(a_i<10^9)\),分别表示编号 1,2……n 的士兵的 IQ 值。
输出:
输出 m+1 的整数,分别是一开始的逆序对数,进行了 i 次操作后的逆序对数。
样例输入:
3 2
2 3 1
1
1
样例输出:
2
1
1
初始的逆序对数为 2,当选中第一个数的时候,后面小于 2 的数只有 1,重排和重编号后变
为 1 3 2,逆序对数变成了 1。再次选中第一个数,后面没有小于 1 的数,所以都不会动,
逆序对数不变。注(IQ 可能有相同的士兵)
数据范围:
对于 40%的数据,\(1 \leq n \leq 20,1 \leq m \leq 20\)。
对于 70%的数据,\(1 \leq n,m \leq 10^3\)。
对于 100%的数据,\(1 \leq n,m \leq 10^5\)
分析
没错,题意就是这样不清楚。输入格式还少了点东西。
所谓的选出来重新排列的意思是提取出一些数,排序后放入原来提取他们的位置上。
考场70分
先离散化,然后直接模拟,用树状数组求逆序对。复杂度\(O(N^2 \log N)\)
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
T data=0;
int w=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
data=10*data+ch-'0',ch=getchar();
return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;
const int MAXN=1e5+7;
int n,a[MAXN],date[MAXN];
int b[MAXN],cnt,pos[MAXN];
int c[MAXN];
inline int lowbit(int x)
{
return x&-x;
}
inline void add(int p,int v)
{
while(p<=n)
{
c[p]+=v;
p+=lowbit(p);
}
}
inline int query(int p)
{
int res=0;
while(p)
{
res+=c[p];
p-=lowbit(p);
}
return res;
}
inline int solve()
{
int ans=0;
for(int i=1;i<=n;++i)
{
ans+=query(n)-query(a[i]);
add(a[i],1);
}
memset(c,0,sizeof(int)*(n+7));
return ans;
}
int main()
{
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
int m;
read(n);read(m);
for(int i=1;i<=n;++i)
date[i]=read(a[i]);
sort(date+1,date+n+1); // discretization
for(int i=1;i<=n;++i)
a[i]=lower_bound(date+1,date+n+1,a[i])-date;
/* for(int i=1;i<=n;++i)
cerr<<a[i]<<" ";
cerr<<endl;*/
printf("%d\n",solve());
while(m--)
{
int x;
read(x);
pos[cnt=1]=x;
b[cnt]=a[x];
for(int i=x;i<=n;++i)
if(a[i]<a[x])
{
pos[++cnt]=i;
b[cnt]=a[i];
}
sort(b+1,b+cnt+1);
for(int i=1;i<=cnt;++i)
a[pos[i]]=b[i];
printf("%d\n",solve());
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
标解
我们考虑计算一个数之后有多少比他小的数来计算整体的逆序对数。可以发现的是,当我们对一个数字进行操作是,所有比他的数对逆序对的贡献是不变,比他小且在他后面的逆序对数的贡献要变成 0。所以对于一个数来说,他的贡献变为 0 的时刻就是在他前面比他大的而且最早进行的操作对应的时刻。这样的话我们就可以用线段树来维护了,当然也可以分治来做,复杂度 \(O(N \log N)\)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <string>
#include <queue>
using namespace std;
typedef long long LL;
#define For(i,a,b) for (int i = (a); i <= (b); i++)
#define Cor(i,a,b) for (int i = (a); i >= (b); i--)
#define rep(i,a) for (int i = 0; i < a; i++)
#define Fill(a,b) memset(a,b,sizeof(a))
const int maxn = 500010;
const int maxm = 1e6 + 10;
const int inf = 1e9;
struct node
{
int v, id, p;
}P[maxn];
int n, m;
int f[maxn];
void init()
{
scanf("%d%d", &n, &m);
For(i, 1, n)
scanf("%d", &P[i].v), P[i].id = i, P[i].p = f[i] = inf;
rep(i, m)
{
int x;
scanf("%d", &x);
if (P[x].p == inf)
P[x].p = f[x] = i;
}
}
struct BIT
{
int val[maxm];
void add(int x, int v)
{
for (int i = x; i < maxm; i += i & (-i))
val[i] += v;
}
int ask(int x)
{
int ret = 0;
for (int i = x; i; i -= i & (-i))
ret += val[i];
return ret;
}
}b;
int g[maxn];
LL F[maxn];
int mn[maxn<<2];
void up(int id,int l,int r,int v,int w)
{
if(l==r){
mn[id]=min(mn[id],w);return;
}
int mid=(l+r)>>1;
if(v<=mid) up(id<<1,l,mid,v,w);
else up(id<<1|1,mid+1,r,v,w);
mn[id]=min(mn[id<<1],mn[id<<1|1]);
}
int q(int id,int l,int r,int tl,int tr)
{
if(tl==l&&r==tr) return mn[id];
int mid=(l+r)>>1;
if(tr<=mid) return q(id<<1,l,mid,tl,tr);
else if(tl>mid) return q(id<<1|1,mid+1,r,tl,tr);
else return min(q(id<<1,l,mid,tl,mid),q(id<<1|1,mid+1,r,mid+1,tr));
}
int a[maxn];
map<int,int> mp;
void solve()
{
LL ans = 0;
Cor(i, n, 1)
{
g[i] = b.ask(P[i].v);
b.add(P[i].v + 1, 1);
ans += g[i];
a[i]=P[i].v;
}
sort(a+1,a+1+n);
int cnt=1;
for(int i=1;i<=n;i++)
{
if(mp[a[i]]) continue;
mp[a[i]]=cnt++;
}
printf("%lld\n", ans);
memset(mn,127,sizeof(mn));
for(int i=1;i<=n;i++)
{
P[i].v=mp[P[i].v];
up(1,1,cnt,P[i].v,P[i].p);
int t=q(1,1,cnt,P[i].v,cnt);
if(t>m) continue;
F[t]+=g[i];
}
rep(i, m)
{
ans -= F[i];
printf("%lld\n", ans);
}
}
int main()
{
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
init();
solve();
return 0;
}
然而实际上这份代码是错的,他对相等的IQ的处理方式不正确。同学有不用线段树的期望\(O(\log N)\)的做法。
试题二
B题
问题描述:
四个机器人,在 3 * 3 的方格里,一开始四个机器人分别站在 9 个格子上,每一步机器人可
以往临近的一个格子移动或留在原地(同一个格子可以有多个机器人停留),经过 n 步后有
多少种不同的走法,使得每个毯子上都有 1 机器人停留。由于方法数量巨大,输出 Mod
\(10^9 + 7\) 的结果。
输入:
第一行包含一个整数 n。
输出:
输出一行输出走法的数量 Mod \(10^9 + 7\)
样例输入:
1
样例输出:
229
数据范围:
对于 40%的数据,\(1 \leq n\leq 10\);
对于 70%的数据,\(1 \leq n \leq 10^6\);
对于 100%的数据,\(1 \leq n \leq 10^{18}\)。
分析
四个机器人在九个格子上?不明所以。
考场爆零做法
打表过样例,随机出奇迹!
标解
实际上按AC了的同学的理解,每个格子上都有一个机器人。
一共只有 9 个格子,我们对它进行编号,可以构成一个 9×9 的转移矩阵,进行快速幂之后 9!的枚举每个棋子最终的位置即可。复杂度 \(O(9^3×\log k+9!)\)。
矩阵元素\(a_{i,j}\)存的是\(i\)是否能一步到\(j(i \neq j)\),\(1(i=j)\)。后面为1是因为可以选择不走。
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
struct Matrix
{
long long a[9][9];
}ma;
Matrix mull(Matrix a,Matrix b)
{
Matrix ans;
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
ans.a[i][j]=0;
for(int k=0;k<9;k++)
ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
}
}
return ans;
}
Matrix fast(long long ci,Matrix num)
{
Matrix ans;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
ans.a[i][j]=(i==j);
while(ci){
if(ci&1) ans=mull(ans,num);
num=mull(num,num);ci=ci>>1;
}
return ans;
}
int a[10];
int main()
{
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
ma.a[i][j]=((abs(i%3-j%3)+abs(i/3-j/3))==1);
for(int i=0;i<9;i++)
ma.a[i][i]=1;
long long n;
scanf("%lld",&n);
ma=fast(n,ma);
for(int i=0;i<9;i++)
a[i]=i;
long long ans=0;
for(int i=0;i<362880;i++)
{
long long tmp=1;
for(int j=0;j<9;j++)
{
tmp=tmp*ma.a[j][a[j]]%mod;
}
ans=(ans+tmp)%mod;
next_permutation(a,a+9);
}
printf("%lld\n",ans);
return 0;
}
试题三
C题
问题描述:
计算 gcd(i,j)的 k 次方的和,其中 \(1 \leq i \leq n,1 \leq j \leq n\);
输入:
第一行包含两个整数 n,k。
输出:
输出一行,表示对应的答案。
样例输入:
2 2
样例输出:
7
数据范围:
对于 40%的数据,\(1 \leq n \leq 10^3\);
对于 70%的数据,\(1 \leq n \leq 10^6\);
对于 100%的数据,\(1 \leq n \leq 10^{10}\),
对于所有的数据 \(1 \leq k \leq 5\)
分析
考场30分(30分?)
设\(f(x)=x^k\),则原式
先\(O(N)\)预处理\(\mu,f\),然后可以\(O(N \ln N)\)预处理它们的狄利克雷卷积,最后\(O(\sqrt{n})\)查询。
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
T data=0;
int w=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
data=10*data+ch-'0',ch=getchar();
return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;
void write(__int128 x)
{
stack<int>S;
while(x)
{
S.push(x%10);
x/=10;
}
while(!S.empty())
{
putchar('0'+S.top());
S.pop();
}
}
const int MAXN=1e6+7;
int n,k;
int prime[MAXN],pcnt;
int mu[MAXN];
void getmu()
{
mu[1]=1,prime[1]=1;
for(int i=2;i<=n;++i)
{
if(!prime[i])
{
prime[++pcnt]=i;
mu[i]=-1;
}
for(int j=1;j<=pcnt&&i*prime[j]<=n;++j)
{
prime[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
}
__int128 f[MAXN];
__int128 qpow(__int128 x,int k)
{
__int128 ans=1;
while(k)
{
if(k&1)
ans*=x;
x*=x,k>>=1;
}
return ans;
}
void getf()
{
for(int i=1;i<=n;++i)
f[i]=qpow(i,k);
}
__int128 g[MAXN];
void getg()
{
for(int i=1;i<=n;++i)
for(int j=1;j*i<=n;++j)
g[i*j]+=mu[i]*f[j];
for(int i=1;i<=n;++i)
g[i]+=g[i-1];
}
__int128 solve()
{
__int128 ans=0;
int j;
for(int i=1;i<=n;i=j+1)
{
j=n/(n/i);
ans+=(__int128)(n/i)*(n/i)*(g[j]-g[i-1]);
/* cerr<<i<<" ";
write(ans);
cerr<<endl;*/
}
return ans;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
read(n);read(k);
getmu();
/* cerr<<"mu:"<<endl;
for(int i=1;i<=n;++i)
cerr<<i<<" mu="<<mu[i]<<endl;*/
getf();
/* cerr<<"f:"<<endl;
for(int i=1;i<=n;++i)
{
cerr<<i<<" f=";
write(f[i]);
cerr<<endl;
}*/
getg();
/* cerr<<"g:"<<endl;
for(int i=1;i<=n;++i)
{
cerr<<i<<" g=";
write(g[i]-g[i-1]);
cerr<<endl;
}*/
write(solve());
// fclose(stdin);
// fclose(stdout);
return 0;
}
然而为什么30分呢?不应该70分吗?
因为题目少说了一句“对\(10^9+7\)取模”。
所以下面的程序是70分的。
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
T data=0;
int w=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
data=10*data+ch-'0',ch=getchar();
return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;
void write(__int128 x)
{
stack<int>S;
while(x)
{
S.push(x%10);
x/=10;
}
while(!S.empty())
{
putchar('0'+S.top());
S.pop();
}
}
const int MAXN=1e6+7;
int n,k;
int prime[MAXN],pcnt;
int mu[MAXN];
void getmu()
{
mu[1]=1,prime[1]=1;
for(int i=2;i<=n;++i)
{
if(!prime[i])
{
prime[++pcnt]=i;
mu[i]=-1;
}
for(int j=1;j<=pcnt&&i*prime[j]<=n;++j)
{
prime[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
}
__int128 f[MAXN];
__int128 qpow(__int128 x,int k)
{
__int128 ans=1;
while(k)
{
if(k&1)
ans*=x;
x*=x,k>>=1;
}
return ans;
}
void getf()
{
for(int i=1;i<=n;++i)
f[i]=qpow(i,k);
}
__int128 g[MAXN];
void getg()
{
for(int i=1;i<=n;++i)
for(int j=1;j*i<=n;++j)
g[i*j]+=mu[i]*f[j];
for(int i=1;i<=n;++i)
g[i]+=g[i-1];
}
__int128 solve()
{
__int128 ans=0;
int j;
for(int i=1;i<=n;i=j+1)
{
j=n/(n/i);
ans+=(__int128)(n/i)*(n/i)*(g[j]-g[i-1]);
/* cerr<<i<<" ";
write(ans);
cerr<<endl;*/
}
return ans;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
read(n);read(k);
getmu();
/* cerr<<"mu:"<<endl;
for(int i=1;i<=n;++i)
cerr<<i<<" mu="<<mu[i]<<endl;*/
getf();
/* cerr<<"f:"<<endl;
for(int i=1;i<=n;++i)
{
cerr<<i<<" f=";
write(f[i]);
cerr<<endl;
}*/
getg();
/* cerr<<"g:"<<endl;
for(int i=1;i<=n;++i)
{
cerr<<i<<" g=";
write(g[i]-g[i-1]);
cerr<<endl;
}*/
write(solve()%int(1e9+7));
// fclose(stdin);
// fclose(stdout);
return 0;
}
WTF?
标解
题解里给的公式有很大的问题,不好复制,我直接放上我的分析。
先定义一个辅助函数\(f\)
再用\(f\)表达出结果
最后推一下\(f\)的前缀和
可用求欧拉函数前缀和的方式求解该式,复杂度\(O(n^{\frac{3}{4}})\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=5000005;
const int mod=1e9+7;
int pri[maxn],fi[maxn],t;
bool flag[maxn];
long long sumfi[maxn];
long long mod2,mod3,mod6,mod12,mod30;
long long fast(long long num,int ci)
{
long long ans=1;
while(ci)
{
if(ci&1) ans=ans*num%mod;
num=num*num%mod;ci=ci>>1;
}
return ans;
}
map<long long ,long long>mp;
void pre()
{
t=0;memset(flag,0,sizeof(flag));
fi[1]=1;
for(int i=2;i<maxn;i++)
{
if(!flag[i]){
pri[t++]=i;fi[i]=i-1;
}
for(int j=0;j<t&&pri[j]*i<maxn;j++)
{
flag[pri[j]*i]=1;
if(i%pri[j]==0){
fi[i*pri[j]]=fi[i]*pri[j];
break;
}
fi[i*pri[j]]=fi[i]*(pri[j]-1);
}
}
for(int i=1;i<maxn;i++)
{
sumfi[i]=sumfi[i-1]+fi[i];
if(sumfi[i]>=mod) sumfi[i]-=mod;
}
mod2=fast(2,mod-2);
mod3=fast(3,mod-2);
mod6=fast(6,mod-2);
mod12=fast(12,mod-2);
mod30=fast(30,mod-2);
mp.clear();
}
long long gett(long long n,int k)
{
n%=mod;
if(k==1)
return n*(n+1)/2%mod;
if(k==2)
return n*(n+1)%mod*(2*n+1)%mod*mod6%mod;
if(k==3){
long long t=n*(n+1)/2%mod;;
return t*t%mod;
}
if(k==4){
return n*(n+1)%mod*(2*n+1)%mod*((3*n*n%mod+3*n-1)%mod)%mod*mod30%mod;
}
if(k==5){
long long t=n*(n+1)%mod;
t=t*t%mod;
t=t*((2*n*n%mod+2*n-1)%mod)%mod;
t=t*mod12%mod;
return t;
}
}
long long solve(long long u)
{
if(u<maxn){
return sumfi[u];
}
if(mp[u]) return mp[u];
long long tmp=u%mod;
long long ans=tmp*(tmp+1)/2%mod;
for(long long i=2,j;i<=u;i=j+1)
{
j=u/(u/i);
ans=(ans-solve(u/i)*(j-i+1))%mod;
}
ans%=mod;
if(ans<0) ans+=mod;
mp[u]=ans;
return ans;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
pre();
long long n;
int k;
scanf("%lld%d",&n,&k);
long long ans=0;
for(long long i=1,j;i<=n;i=j+1)
{
j=n/(n/i);
ans=(ans+solve(n/i)*(gett(j,k)-gett(i-1,k)))%mod;
}
ans=ans*2;
ans=ans-gett(n,k);
ans%=mod;
if(ans<0) ans+=mod;
printf("%lld\n",ans);
return 0;
}
但是杜教筛可以做到\(O(n^{\frac{2}{3}})\),并且标程也是\(O(n^{\frac{2}{3}})\)的。