HGOI7.17集训题解

题解

今天的题…错误还比较多…但..错的是题面……所以今天的高分真的是水出来的……我真的是个垃圾OIer


第一题——序列(sequence)

【题目描述】

  • 给出一个长度为3N的正整数序列。要求一个长度为2N的子序列,使得该子序列的前面N个元素的和减去后面N个元素的和最大。求这个最大值(题干就这么长

  • 刚拿到手之后没有什么思路,但是想了想之后发现:
  • 前面的n个元素是一定归前面所选择的,同理后面n个元素也是归后面选择的。那就只要枚举中间端点来进行前后的分块,用大根堆和小根堆维护前面的最大值和后面的最小值就可以。然后扫一遍求出答案。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define LL long long
using namespace std;
void fff(){
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
}
const int MAXN=300100;
int n;
LL a[MAXN];
priority_queue<LL,vector<LL>,less<LL> > q2;
priority_queue<LL,vector<LL>,greater<LL> > q1;
LL f1[MAXN],f2[MAXN];
LL s1,s2,maxx;
int main(){
    fff();
    scanf("%d",&n);
    s1=0,s2=0;
    maxx=0;
    for (int i=1;i<=3*n;i++){
        scanf("%lld",&a[i]);
    }
    for (int i=1;i<=n;i++){
        q1.push(a[i]);
        s1+=a[i];
        q2.push(a[3*n-i+1]);
        s2+=a[3*n-i+1];
    }
    f1[n]=s1;
    for (int i=n+1;i<=2*n;i++){
        f1[i]=f1[i-1];
        if(a[i]>q1.top()){
            f1[i]-=q1.top();
            f1[i]+=a[i];
            q1.pop();
            q1.push(a[i]);
        }
    }
    f2[2*n+1]=s2;
    for (int i=2*n;i>n;i--){
        f2[i]=f2[i+1];
        if(a[i]<q2.top()){
            f2[i]-=q2.top();
            f2[i]+=a[i];
            q2.pop();
            q2.push(a[i]);
        }
        maxx=max(maxx,f1[i]-f2[i+1]);
    }
    printf("%lld",maxx);
    return 0;
}

第二题——最大公约数(gcd)

【题目描述】

  • 给出2个正整数数组 a[0..n1] a [ 0.. n − 1 ] b[0..m1] b [ 0.. m − 1 ] ,定义nm二维数组 c[i][j]=gcd(a[i],b[j]) c [ i ] [ j ] = g c d ( a [ i ] , b [ j ] ) ,给出q个形如 r1,c1,r2,c2 r 1 , c 1 , r 2 , c 2 的询问,询问 c[r1..r2][c1..c2] c [ r 1.. r 2 ] [ c 1.. c 2 ] 这个子矩形里有多少个不同的数。

  • 讲道理这道题我只会打爆力。骗了40分。
  • 正解还是用倍数法来做。(lzw4896s的惯用打法)
  • f[i] f [ i ] 表示从 a[r1..r2] a [ r 1 . . r 2 ] b[r1..r2] b [ r 1 . . r 2 ] 取出一对, gcd g c d 刚好为 i i 的倍数的方案。
  • g[i]表示从 a[r1..r2] a [ r 1 . . r 2 ] b[r1..r2] b [ r 1 . . r 2 ] 取出一对, gcd g c d 刚好为 i i 的方案。
  • 那么可以递推求出g[i]=f[i]g[2i]g[3i].....
  • f[i] f [ i ] 可以在先前进行预处理:( a[i] a [ i ] 当中是 i i 的倍数有多少个)*(b[i]当中是 i i 的倍数有多少个)。复杂度就是O(nlogn)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 100010
#define LL long long
using namespace std;
void fff(){
    freopen("gcd.in","r",stdin);
    freopen("gcd.out","w",stdout);
}
const int MAXN=100010;
int n,m,q,cnt;
int a[MAXN],b[MAXN];
bool visited[MAXN*12];
int cnt1[MAXN],cnt2[MAXN],d1[MAXN],d2[MAXN];
LL f[MAXN],g[MAXN];
int gcd(int a,int b){
    return (b==0)?a:gcd(b,a%b);
}
int solve(int r1,int r2,int c1,int c2){
    memset(cnt1,0,sizeof(cnt1));
    memset(cnt2,0,sizeof(cnt2));
    memset(d1,0,sizeof(d1));
    memset(d2,0,sizeof(d2));
    for (int i=r1;i<=r2;i++) cnt1[a[i]]++;
    for (int i=c1;i<=c2;i++) cnt2[b[i]]++;
    for (int i=1;i<N;i++){
        for (int j=i;j<N;j+=i)
            d1[i]+=cnt1[j];
    }
    for (int i=1;i<N;i++){
        for (int j=i;j<N;j+=i)
            d2[i]+=cnt2[j];
    }
    int res=0;
    for (int i=1;i<N;i++) g[i]=1ll*d1[i]*d2[i];
    for (int i=N-1;i>=1;i--){
        f[i]=g[i];
        for (int j=2*i;j<N;j+=i) f[i]-=f[j];
        if(f[i]) res++;
    }
    return res;
}
int main(){
    fff();
    scanf("%d%d%d",&n,&m,&q);
    for (int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    for (int i=0;i<m;i++){
        scanf("%d",&b[i]);
    }
    for (int i=1;i<=q;i++){
        int r1,r2,c2,c1;
        scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
        memset(visited,false,sizeof(visited));
        cnt=0;
        printf("%d\n",solve(r1,r2,c1,c2));
    }
    return 0;
}

第三题——染色(color)

【题目描述】

  • N个点M条边的无向无环图,无重边和自环。一开始所有的点都是颜色0
    。接下来有Q次操作,每次操作形如x d c表示将与点x距离不超过d的点全部染成颜色c(同一个点之后染的颜色会覆盖之前的)。问Q次操作结束后每个点的颜色。

  • 讲道理..原来题干是有向无环图但std打的是无向图….然后就爆零了。改成有向图再测,只有四十分。(毕竟是暴力算法)
  • 在之前又想到过正解。由于会被覆盖,则只需要采取一种离线算法。从后往前进行覆盖。保证每一个点只会被读到一次。但会发现。当当前这个节点已经被覆盖了之后,他身后的节点在下一次染色的时候无法被读取。就很尴尬了……考场上没有办法解决这个问题。而标程利用d很好的解决了这个问题。
  • 把染色数组改变成一个二维数组。 color[N][d] c o l o r [ N ] [ d ] 表示在第N个点第d步被染过色。这样子可以有效地预防后效,同时也能避免对下一步的子节点有影响。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <vector>
using namespace std;
void fff(){
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
}
const int MAXN=101000;
int n,m,q;
struct node{
    int x,d,c;
}Q[MAXN];
queue <node>quq;
vector <int> G[MAXN];
bool visited[MAXN];
int color[MAXN][11];
void solve(int x,int d,int c){
    if(color[x][d]) return;
    color[x][d]=c;
    if(d==0) return;
    int siz=G[x].size();
    for (int i=0;i<siz;i++){
        solve(G[x][i],d-1,c);
    }
    solve(x,d-1,c);
}
int main(){
    fff();
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    scanf("%d",&q);
    for (int i=1;i<=q;i++){
        int x,d,c;
        scanf("%d%d%d",&Q[i].x,&Q[i].d,&Q[i].c);
    }
    for (int i=q;i>=1;i--){
        solve(Q[i].x,Q[i].d,Q[i].c);
    }
    for(int i=1;i<=n;i++){
        printf("%d\n",color[i][0]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值