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分?)

\[\sum_{t=1}^n\sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=k]\cdot t^k\\ =\sum_{t=1}^nt^k\sum_{i=1}^{\lfloor n/t\rfloor}\sum_{j=1}^{\lfloor n/t\rfloor}[gcd(i,j)=1]\\ =\sum_{t=1}^{n}t^k\sum_{i=1}^{\lfloor n/t\rfloor}\sum_{j=1}^{\lfloor n/t\rfloor}\sum_{d|i\wedge d|j}\mu(d)\\ =\sum_{t=1}^nt^k\sum_{d=1}^{\lfloor n/t \rfloor}\lfloor\frac{n}{dt}\rfloor^2\mu(d)\\ =\sum_{x=1}^{n}\lfloor\frac{n}{x}\rfloor^2\sum_{d|x}\mu(d)(\frac{x}{d})^k \]

\(f(x)=x^k\),则原式

\[=\sum_{x=1}^{n}\lfloor\frac{n}{x}\rfloor^2(\mu*f)(x) \]

\(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(n)=\sum_{i=1}^n\gcd(i,n)^k\\ =\sum_{d|n}\sum_{i=1}^nd^k[\gcd(i,n)=d]\\ =\sum_{d|n}d^k\sum_{i=1}^n[\gcd(\frac{i}{d},\frac{n}{d})=1]\\ =\sum_{d|n}d^k\varphi(\frac{n}{d})\\ =\sum_{d|n}(\frac{n}{d})^k\varphi(d) \]

再用\(f\)表达出结果

\[\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j)^k\\ =2 \cdot \sum_{i=1}^nf(i)-\sum_{i=1}^ni^k \]

最后推一下\(f\)的前缀和

\[\sum_{i=1}^nf(i)\\ =\sum_{i=1}^n\sum_{d|i}(\frac{i}{d})^k\varphi(d)\\ =\sum_{t=1}^nt^k\sum_{d=1}^{\lfloor n/t\rfloor}\varphi(d)\\ =\sum_{t=1}^nt^kF(\lfloor\frac{n}{t}\rfloor) \]

可用求欧拉函数前缀和的方式求解该式,复杂度\(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}})\)的。

posted on 2018-08-30 15:48  autoint  阅读(159)  评论(0编辑  收藏  举报

导航