【C++】NOI Online能力测试 提高组题目+解析+代码

NOI Online能力测试 提高组题目+解析+代码

  • 序列
    • 【题目描述】
    • 【输入格式】
    • 【输出格式】
    • 【样例1输入】
    • 【样例1输出】
    • 【样例1解释】
    • 【数据范围与提示】
    • 【时间限制】
    • 【空间限制】
    • 【上传文件】
    • 【解析】
    • 【代码】
  • 冒泡排序
    • 【题目描述】
    • 【输入格式】
    • 【输出格式】
    • 【样例1输入】
    • 【样例1输出】
    • 【样例1解释】
    • 【数据范围与提示】
    • 【时间限制】
    • 【空间限制】
    • 【上传文件】
    • 【解析】
    • 【代码】
  • 最小环
    • 【题目描述】
    • 【输入格式】
    • 【输出格式】
    • 【样例1输入】
    • 【样例1输出】
    • 【样例1解释】
    • 【样例2输入】
    • 【样例2输出】
    • 【样例2解释】
    • 【数据范围与提示】
    • 【时间限制】
    • 【空间限制】
    • 【上传文件】
    • 【解析】
    • 【代码】

本文的解析由杭州二中信奥队提供,有删改

序列

【题目描述】

小 D 有一个长度为 n 的整数序列 ai(下标从 1 开始编号,下同),她想通过若干次操作把它变成序列 bi。

小 D 有 m种可选的操作,第 i 种操作可使用三元组 (ti,ui,vi) 描述:若 ti=1,则她可以使 aui 与 avi 都加一或都减一;若 ti=2,则她可以使 aui-1、avi+1,或是 aui+1、avi-1,因此当 ui=vi 时,这种操作相当于没有操作。

小 D 可以以任意顺序执行操作,且每种操作都可进行无限次。现在给定序列与所有操作,请你帮她判断是否存在一种方案能将 ai 变为 bi。题目保证两个序列长度都为 n。若方案存在请输出 YES,否则输出 NO

【输入格式】

从 sequence.in 读入数据。
本题输入文件包含多组数据。
第一行一个正整数 T 表示数据组数。对于每组数据:
第一行两个整数 n,m,表示序列长度与操作种数。
第二行 n 个整数表示序列 ai。
第三行 n 个整数表示序列 bi。
接下来 m 行每行三个整数 ti,ui,vi,第 i 行描述操作 i。
注意:同一个三元组 (ti,ui,vi) 可能在输入中出现多次。

【输出格式】

输出到文件 sequence.out 中。
对于每组数据输出一行一个字符串 YESNO 表示答案。

【样例1输入】

3
1 1
1
3
1 1 1
2 3
1 2
4 5
1 1 2
2 1 2
1 1 2
3 3
1 2 3
5 5 4
1 1 2
1 1 3
2 2 3

【样例1输出】

YES
YES
YES

【样例1解释】

第一组数据:使用一次操作 1。第二组数据:使用三次操作 1。第三组数据:使用三次操作 1,令 a1,a2 都增加 3,再使用一次操作 2,令 a1,a3 都增加 1。

【数据范围与提示】

对于测试点 1 - 5:n=2,m=1,ai,bi ≤ 99,u1 ≠ v1,t1=1。
对于测试点 6 - 10:n=2,m=1,ai,bi ≤ 99,u1 ≠ v1,t1=2。
对于测试点 11 - 12:n=2,ai,bi ≤ 99,ui ≠ vi。
对于测试点 13 - 16:ti=2。
对于测试点 17:n,m ≤ 20。
对于测试点 18:n,m ≤ 1000。
对于所有测试点:1 ≤ T ≤ 10,1 ≤ n,m ≤ 105,1 ≤ ai,bi ≤ 109,ti ∈ {1,2},1≤ ui,vi≤ n。

【时间限制】

2.0s

【空间限制】

256MB

【上传文件】

上传c, cpp或pas语言源程序,文件名应依次为sequence.c, sequence.cpp, sequence.pas。

【解析】

我们令vali =ai - bi ,显然只需要对val数组进行操作,使它变成全0即可。
把第二种操作提取出来,在ui和vi之间连一边,在某个联通块内如果val的和等于0,就可以构造出一组使这个联通块变为全0的方案。我们将其缩点,就变成了没有二操作的情况,这个时候考虑一个联通块是不是二分图,如果是二分图, 只需要判断二分图两边的val之和是否相等,否则判断val之和的奇偶性。构造方案留作思考。

【代码】

#include 
#include 
#include 
#include 
#include 
#include 

#define R                  register int
#define re(i,a,b)          for(R i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

template <typename T>
inline void read(T &x){
	x=0; 
	char c=0; 
	T w=0;  
	while (!isdigit(c)) w|=c=='-',c=getchar();  
	while (isdigit(c)) x=x*10+(c^48),c=getchar();  
	if(w) x=-x;  
}

template <typename T>
inline void print(T x) {
    if(x<0) putchar('-'),x=-x;
    if(x<10) putchar(x+'0');
        else print(x/10),putchar(x%10+'0');
}

int const N=1e5+5;

int T,n,m,cnt,flag;
int a[N],b[N],t[N],u[N],v[N],col[N],f[N],dep[N];
LL sum[N],s[2];
vector <int> adj[N];

inline int find(int x) {
    return !f[x] ? x : f[x]=find(f[x]);
}

void dfs(int u,int fa) {
    s[dep[u]&1]+=sum[u];
    for(int i=0; i<adj[u].size(); i++) {
        int v=adj[u][i];
        if(v==fa) continue;
        if(!dep[v]) {
            dep[v]=dep[u]+1;
            dfs(v,u);
        } else if(((dep[u]+dep[v])&1)==0) flag=1;
    }
}

int main() {
    //freopen("sequence.in","r",stdin);
    //freopen("sequence.out","w",stdout);
    read(T);
    while(T--) {
        memset(dep,0,sizeof(dep));
        memset(sum,0,sizeof(sum));
        memset(f,0,sizeof(f));
        cnt=0;
        read(n),read(m);
        for(int i=1; i<=n; i++) read(a[i]);
        for(int i=1; i<=n; i++) read(b[i]);
        for(int i=1; i<=m; i++) {
            read(t[i]),read(u[i]),read(v[i]);
            if(t[i]==2) {
                if(find(u[i])!=find(v[i])) {
                    f[find(u[i])]=find(v[i]);
                }
            }
        }
        for(int i=1; i<=n; i++) if(find(i)==i) 
            col[i]=++cnt;
        for(int i=1; i<=n; i++) col[i]=col[find(i)],sum[col[i]]+=(a[i]-b[i]);
        for(int i=1; i<=cnt; i++) adj[i].clear();
        for(int i=1; i<=m; i++) {
            if(t[i]==1) {
                adj[col[u[i]]].push_back(col[v[i]]);
                adj[col[v[i]]].push_back(col[u[i]]);
            }
        }
        int ans=1;
        for(int i=1; i<=cnt; i++) {
            if(!dep[i]) {
                dep[i]=1;
                flag=0;
                s[0]=s[1]=0;
                dfs(i,0);
                if(flag) {
                    if((s[0]+s[1])&1) {
                        ans=0;
                        break;
                    }
                } else {
                    if(s[0]!=s[1]) {
                        ans=0;
                        break;
                    }
                }
            }
        }
        if(ans) printf("YES\n");
            else printf("NO\n");
    }
	//fclose(stdin);
	//fclose(stdout);
    return 0;
}

冒泡排序

【题目描述】

给定一个 1 ~ n 的排列 pi,接下来有 m 次操作,操作共两种:

  1. 交换操作:给定 x,将当前排列中的第 x 个数与第 x+1 个数交换位置。
  2. 询问操作:给定 k,请你求出当前排列经过 k 轮冒泡排序后的逆序对个数。
    对一个长度为 n 的排列 pi 进行一轮冒泡排序的伪代码如下:
    for i = 1 to n-1 :
        if p[i] > p[i + 1] :
            swap(p[i], p[i + 1])

【输入格式】

从文件 bubble.in 中读入数据。
第一行两个整数 n,m,表示排列长度与操作个数。
第二行 n 个整数表示排列 pi。
接下来 m 行每行两个整数 ti,ci,描述一次操作:若 ti=1,则本次操作是交换操作,x=ci;若 ti=2,则本次操作是询问操作,k=ci。

【输出格式】

输出到文件 bubble.out 中。
对于每次询问操作输出一行一个整数表示答案。

【样例1输入】

3 6
1 2 3
2 0
1 1
1 2
2 0
2 1
2 2

【样例1输出】

0
2
1
0

【样例1解释】

第一次操作:排列为 {1,2,3},经过 0 轮冒泡排序后为 {1,2,3},0 个逆序对。
第二次操作:排列变为 {2,1,3}。
第三次操作:排列变为 {2,3,1}。
第四次操作:经过 0 轮冒泡排序后排列变为 {2,3,1},2 个逆序对。
第五次操作:经过 1 轮冒泡排序后排列变为 {2,1,3},1 个逆序对。
第六次操作:经过 2 轮冒泡排序后排列变为 {1,2,3},0 个逆序对。

【数据范围与提示】

对于测试点 1 ~ 2:n,m ≤ 100。
对于测试点 3 ~ 4:n,m ≤ 2000。
对于测试点 5 ~ 6:交换操作个数不超过 100。
对于所有测试点:2 ≤ n,m ≤ 2 × 105,ti ∈ {1,2},1 ≤ x < n,0 ≤ k < 231。

【时间限制】

1.0s

【空间限制】

256MB

【上传文件】

上传c, cpp或pas语言源程序,文件名应依次为bubble.c, bubble.cpp, bubble.pas。

【解析】

定义 v i v_i vi表示第 i i i个数之前大于 p i p_i pi的数的个数,逆序对数是 v i v_i vi的和。
每经过一轮冒泡排序,如果 v i v_i vi不为0,就会有一个大于 p i p_i pi的数从它前面到它后面,经过k轮冒泡排序后, v i v_i vi会变成 m a x ( 0 , v i − k ) max(0,v_i- k) max(0,vik)
我们考虑当前序列经过 0 0 0 - n − 1 n- 1 n1次冒泡的答案如何高效维护。先考虑交换 p i p_i pi p p pi+1时对 v i v_i vi v v vi+1的影响:

  • p i p_i pi < < < p p pi+1 v i v_i vi v v vi+1互换, v v vi+1还要加上1,因为多了-个比它大的数被换到它前面了。
  • p i p_i pi > > > p p pi+1 v i v_i vi v v vi+1互换, v i v_i vi要减1,因为有一个原先比它大的数被换到了前面。

我们再考虑 v v v的变化对答案产生的影响:对于一个 v i v_i vi=x,经过 k k k轮冒泡排序后,它产生的贡献是
m a x ( 0 , x − k ) max(0,x - k) max(0,xk),如果变成了 v i = x + 1 v_i=x+1 vi=x+1,经过 0 − x 0 - x 0x 轮冒泡排序的答案都要加一,变成 v i = x − 1 v_i=x- 1 vi=x1类似,需要的操作只有区间加减一和单点查询,用树状数组即可。
树状数组总结:点击这里

【代码】

#include 
#include 
#include 
#include 
#include 
#include 

#define R                  register int
#define re(i,a,b)          for(R i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

template <typename T>
inline void read(T &x){
	x=0; 
	char c=0; 
	T w=0;  
	while (!isdigit(c)) w|=c=='-',c=getchar();  
	while (isdigit(c)) x=x*10+(c^48),c=getchar();  
	if(w) x=-x;  
}

template <typename T>
inline void print(T x) {
    if(x<0) putchar('-'),x=-x;
    if(x<10) putchar(x+'0');
        else print(x/10),putchar(x%10+'0');
}

template <typename T>
inline void print(T x,char t) {
    print(x);
    putchar(t);
}

int const N=2e5+5;

int n,m,now;
int p[N],val[N],cnt[N];
LL sum;
LL f[N];

inline int lowbit(int x) {
    return x & -x;
}

inline void add(int x,LL y) {
    ++x;
    while(x<=n) {
        f[x]+=y;
        x+=lowbit(x);
    }
}

LL query(int x) {
    ++x;
    LL ans=0;
    while(x) {
        ans+=f[x];
        x=x^lowbit(x);
    }
    return ans;
}

int main() {
    //freopen("bubble.in","r",stdin);
    //freopen("bubble.out","w",stdout);
    read(n),read(m);
    for(int i=1; i<=n; i++) read(p[i]);
    for(int i=1; i<=n; i++) {
        val[i]=i-1-query(p[i]-2);
        sum+=val[i];
        add(p[i]-1,1);
        ++cnt[val[i]];
    }
    memset(f,0,sizeof(f));
    add(0,sum);
    now=n;
    for(int i=0; i<n-1; i++) {
        now-=cnt[i];
        add(i+1,-now);
    }
    for(int i=1; i<=m; i++) {
        int opt,x;
        read(opt),read(x);
        if(opt==1) {
            if(p[x]<p[x+1]) {
                swap(val[x],val[x+1]);
                ++val[x+1];
                add(0,1);
                add(val[x+1],-1);
                swap(p[x],p[x+1]);
            } else {
                swap(val[x],val[x+1]);
                add(0,-1);
                add(val[x],1);
                --val[x];
                swap(p[x],p[x+1]);
            }
        }
        if(opt==2) {
            if(x>=n) print(0,'\n');
                else print(query(x),'\n');
        }
    }
	//fclose(stdin);
	//fclose(stdout);
    return 0;
}

最小环

【题目描述】

给定一个长度为 n 的正整数序列 ai,下标从 1 开始编号。我们将该序列视为一个首尾相邻的环,更具体地,对于下标为 i,j(i ≤ j) 的两个数 ai,aj,它们的距离为 min(j-i,i+n-j)。

现在再给定 m 个整数 k1,k2,…,km,对每个 ki(i=1,2,…,m),你需要将上面的序列 ai 重新排列,使得环上任意两个距离为 ki 的数字的乘积之和最大。

【输入格式】

从文件ring.in中读入数据。
第一行两个正整数 n,m,表示序列长度与询问数。
接下来一行 n 个正整数表示 ai。
接下来 m 行每行一个非负整数表示 ki。

【输出格式】

输出到文件ring.out中。
共 m 行,每行一个整数表示答案。

【样例1输入】

6 3
1 2 3 4 5 6
0
1
2

【样例1输出】

91
82
85

【样例1解释】

ki=0 时:答案为每个数平方的和。
ki=1 时:一种最优方案:{3,1,2,4,6,5}。
答案为 3 × 1 + 1 × 2 + 2 × 4 + 4 × 6 + 6 × 5 + 5 × 3 = 82。
ki=2 时:一种最优方案:{3,6,1,4,2,5}。
答案为 3 × 1 + 1 × 2 + 2 × 3 + 6 × 4 + 4 × 5 + 5 × 6 = 85。

【样例2输入】

6 1
1 2 3 4 5 6
3

【样例2输出】

88

【样例2解释】

附加说明:样例当 k=3 时,一个合法的排列是 1,5,3,2,6,4,答案为 88。注意这里答案不是 44。

【数据范围与提示】

对于所有测试数据:1 ≤ m ≤ n ≤ 2 × 105,0 ≤ k ≤ ⌊n/2⌋,1 ≤ ai ≤ 105。
每个测试点的具体限制见下表:

测试点编号 n ≤ 特殊性质
1 10
2 18
3 36 n 为偶数且 m=1,k=2
4,5 1000 m ≤ 10,k=1
6 50 m ≤ 10,k ≤ 2
7,8 3000
9,10 2 × 105

【时间限制】

2.0s

【空间限制】

256MB

【上传文件】

上传c, cpp或pas语言源程序,文件名应依次为ring.c, ring.cpp, ring.pas。

【解析】

对一个 k = x k= x k=x的答案,和对 k = g c d ( x , n ) k= gcd(x, n) k=gcd(x,n)的答案是一样的, 200000 以内因数个数最多的数有160个因数,只需要算出这160个因数的答案即可。

一个因数 d d d可以把 n n n个位置拆成 n / d n/d n/d组,组与组之间相互独立,每组都相当于有 d d d个空位, k = 1 k= 1 k=1的问题。打表发现最大的 d d d个数会被分在一组,次大的 d d d个数分在一组,以此类推。一组内的 d d d个数从大到小依次为 a 1 , a 2 , … … , a d a_1,a_2,……,a_d a1,a2,,ad,最优方案是 … a 7 , a 5 , a 3 , a 1 , a 2 , a 4 , a 6 … …a_7,a_5,a_3,a_1,a_2,a_4,a_6… a7,a5,a3,a1,a2,a4,a6。对每个因数都求一下就行了。

【代码】

#include 
#include 
#include 
#include 
#include 
#include 

#define R                  register int
#define re(i,a,b)          for(R i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

template <typename T>
inline void read(T &x){
	x=0; 
	char c=0; 
	T w=0;  
	while (!isdigit(c)) w|=c=='-',c=getchar();  
	while (isdigit(c)) x=x*10+(c^48),c=getchar();  
	if(w) x=-x;  
}

template <typename T>
inline void print(T x) {
    if(x<0) putchar('-'),x=-x;
    if(x<10) putchar(x+'0');
        else print(x/10),putchar(x%10+'0');
}

template <typename T>
inline void print(T x,char t) {
    print(x);
    putchar(t);
}

int const N=2e5+5;

int n,m;
int a[N];
LL ans[N];

inline int gcd(int a,int b) {
    return b ? gcd(b,a%b) : a;
}

LL solve(int d) {
    if(ans[d]) return ans[d];
    if(d==1) {
        for(int i=1; i<=n; i++) ans[d]+=1LL*a[i]*a[i];
        return ans[d];
    }
    if(d==2) {
        for(int i=1; i<=n; i+=2) ans[d]+=1LL*a[i]*a[i+1];
        ans[d]<<=1;
        return ans[d]; 
    }
    for(int i=n; i>=1; i-=d) {
        if(d&1) {
            ans[d]+=1LL*a[i]*a[i-1];
            ans[d]+=1LL*a[i]*a[i-2];
            for(int j=i-1; j-2>=i-d+1; j--) ans[d]+=1LL*a[j]*a[j-2];
            ans[d]+=1LL*a[i-d+1]*a[i-d+2];
        } else {
            ans[d]+=1LL*a[i]*a[i-1];
            ans[d]+=1LL*a[i]*a[i-2];
            for(int j=i-1; j-2>=i-d+2; j--) ans[d]+=1LL*a[j]*a[j-2];
            ans[d]+=1LL*a[i-d+1]*a[i-d+2];
            ans[d]+=1LL*a[i-d+1]*a[i-d+3];
        }
    }
    return ans[d];
}

int main() {
    //freopen("ring.in","r",stdin);
    //freopen("ring.out","w",stdout);
    read(n),read(m);
    for(int i=1; i<=n; i++) read(a[i]);
    sort(a+1,a+n+1);
    for(int i=1; i<=m; i++) {
        int x;
        read(x);
        print(solve(n/gcd(n,x)),'\n');
    }
	//fclose(stdin);
	//fclose(stdout);
    return 0;
}

你可能感兴趣的:(China,Computer,Federation,#,NOI,Online,【推荐专栏】成套题解)