HGOI7.6集训题解

题解

今天做了2012的day2。果然当年逼得省一线只有260的题目真的不好做。day2只有170分草草收场。觉得拿不了高分。

第一题——同余方程(mod)

【题目描述】求最小的正整数x使得 a x 1 ( m o d b ) 成立。

  • 看到这种题目果断先变形得 a x m o d b = 1
  • 那么再变型, a x = y b + 1
  • 再来一步, a x + b y = 1 = g c d ( a , b )
  • OK我们可以从这里看得出来,这个就是欧几里得拓展的板子了。而方程要有解就要使得 g c d ( a , b ) = 1 ,则其中由欧几里得算法的最后一步得出当 b = 0 x 0 = 0 , y 0 = 1 a x + b y = g c d ( a , b ) 一组解
  • 而根据欧几里得拓展的根的规律, x = y 0 , y = x 0 a / b y 0 ,接下来只要求递推就好了。

所以不好好学数学你怎么搬砖

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define LL long long
using namespace std;
void fff(){
    freopen("mod.in","r",stdin);
    freopen("mod.out","w",stdout);
}
void gcd(LL a,LL b,LL &d,LL &x,LL &y){//朴实欧几里得拓展gcd,d就是gcd(a,b)的值
    if(!b){
        x=1;
        y=0;
        d=a;
    }else{
        gcd(b,a%b,d,x,y);
        int t=x;
        x=y;
        y=t-a/b*y;//x = y_0, y = x_0 – a / b * y_0递归回来
    }
}
long long a,b,d,x,y;
int main(){
//  fff();
    cin>>a>>b;
    gcd(a,b,d,x,y);
    cout<<(x/d%(b/d)+(b/d))%(b/d);//由于要求出最小根,那么有可能求出的不是最优解那就取个膜,去摸范围移动到[1,b],就可以求出最小整数解了。
    //由于从上面得出,gcd(a,b)=1,那么gcd(ax,b)=gcd(x,b)=d,所以d|x且d|b
    return 0;
}

第二题——借教室(classroom)

【题目描述】

给定n间教室和m张订单,订单上给定左右断点和所需要借的数量,按照顺序来判断哪张订单是不合法的。
  • 我*!这个区间查询区间更新,不是线段树分块等等的板子么!我秒出算法打起代码来我就是一把梭!抄起键盘就是干!让你见识什么叫做钢枪侠!我15分钟搞定分块打法然后…..果断超时拿了70分orz
    这里写图片描述
  • 分析了下,分块的算法时间复杂度是 O ( m n ) ,但是明显超过了1s,我的鹅子dasxxx打了线段树最大数据1.2s(复杂程度 m l o g 2 n )。

看我70分的分块,代码是真的漂亮。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define INF 0x3f3ffffff
#define LL long long
using namespace std;
void fff(){
    freopen("classroom.in","r",stdin);
    freopen("classroom.out","w",stdout);
}
const int MAXN=1000110;
int n,m,blo[MAXN];
LL a[MAXN],tag[MAXN],minn[MAXN];
int blok_size;
bool query(int l,int r,LL num){
    for (int i=l;i<=min(blo[l]*blok_size,r);i++){
        if(a[i]<tag[blo[i]]+num) return false;
    }
    if(blo[l]<blo[r]){
        for (int i=(blo[r]-1)*blok_size+1;i<=r;i++){
            if(a[i]<tag[blo[i]]+num)
                return false;
        }
        for (int i=blo[l]+1;i<blo[r];i++){
            if(minn[i]<tag[i]+num) return false;
        }
    }
    return true;
}
void Add(int l,int r,LL num){
    for (int i=(blo[l]-1)*blok_size+1;i<=min(blo[l]*blok_size,r);i++){
        if(i>=l){
            a[i]-=num;
        }
        a[i]-=tag[blo[i]];

        minn[blo[i]]=min(minn[blo[i]],a[i]);
    }
    tag[blo[l]]=0;
    if(blo[l]<blo[r]){
        for (int i=(blo[r]-1)*blok_size+1;i<=min(blo[r]*blok_size,n);i++){
            if(i<=r){
                a[i]-=num;
            }
            a[i]-=tag[blo[i]];

            minn[blo[i]]=min(minn[blo[i]],a[i]);
        }
        tag[blo[r]]=0;
        for (int i=blo[l]+1;i<blo[r];i++){
            tag[i]+=num;
        }
    }
}
int main(){
    fff();
    cin>>n>>m;
    blok_size=sqrt(n);

    for (int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    memset(tag,0,sizeof(tag));
    for (int i=1;i<=(n-1)/blok_size+1;i++) minn[i]=1000090000;
    for (int i=1;i<=n;i++){
        blo[i]=(i-1)/blok_size+1;
        minn[blo[i]]=min(minn[blo[i]],a[i]);
    }
    for (int i=1;i<=m;i++){
        int l,r,num;
        scanf("%lld%d%d",&num,&l,&r);
        if(query(l,r,num)){
            Add(l,r,num);
        }else{
            cout<<-1<<'\n'<<i;
            return 0;
        }
    }
    cout<<0;
    return 0;
}
  • 那么什么算法能够解决。
  • 感谢xyc给出差分算法(复杂程度 O ( n l o g 2 m ) ,大概就赚了 m < n 的便宜吧)。由于每一个订单订的左节点开始要减去相同数量的 n u m i 直至右节点。那么就设置diff数组,从1号节点开始向后扫描,用 O ( n ) 的时间来更新完所有的节点。
  • 为了节省时间,则需要二分来枚举订单编号,直至找到那一个不合法的订单。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define INF 0x3f3ffffff
#define LL long long
using namespace std;
void fff(){
    freopen("classroom.in","r",stdin);
    freopen("classroom.out","w",stdout);
}
const int MAXN=1001000;
int n,m;
int a[MAXN];
int l[MAXN],r[MAXN],d[MAXN];
int diff[MAXN],need[MAXN];
bool query(int x){
    memset(diff,0,sizeof(diff));
    for (int i=1;i<=x;i++){
        diff[l[i]]+=d[i];//枚举到x张订单的头和尾,并对头上减去,尾的下一个点加上
        diff[r[i]+1]-=d[i];
    }
    for (int i=1;i<=n;i++){
        need[i]=need[i-1]+diff[i];//递推向后更新。
        if(need[i]>a[i]) return false;
    }
    return true;
}
int main(){
//  fff();
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++) scanf("%d%d%d",&d[i],&l[i],&r[i]);
    int begin=1,end=m;
    if(query(m)){//如果最后一张的情况都是合法的那就是0
        cout<<0;
        return 0;
    }
    while (begin<end){//二分寻找答案
        int mid=(begin+end)/2;
        if(query(mid)) begin=mid+1;
        else{
            end=mid;
        }
    }
    cout<<-1<<'\n'<<begin;

    return 0;
}

第三题——疫情控制(blockade)

【题目描述】
- 给你一棵树,和m个军队在各个节点之间设卡口,使得叶子结点和根节点被军队阻断。求军队最少走多少路可以满足条件(军队可以同时动,而最终的结果是走路最多的军队的长度,即最大值的最小值)但不能在根上设立卡口。

【算法】

  • 首先要明确几点:
  • 1、军队越往上(根)走所能触及到的叶子结点更多,除非越过根节点,否则往下走就是在浪费时间
  • 2、军队越过根节点之后如果没有其他军队在原来位置上或者说管辖原位置下的叶子结点,那么这个节点走了也是在浪费时间(因为走了之后还是要调别的军队过来补上位置,那干脆就不要走)
  • 3、由于求的是最大值的最小值,那么果断二分时间,让军队尽可能的多走。如果有的军队在当前的时间下不能到达根节点,那么就走到top,而能够走到根节点的军队,就要进行判断(包括有没有其他军队管住自己下面的根节点,自己越过去需不需要花时间),如果不需要那么久停在原地。
  • 4、由于线性操作肯定会爆炸,那么我们就用倍增的方法预处理每一个节点,然后再来进行军队的移动。
  • 大致就这么四点吧。接下来就是算法的实现问题了。比较复杂。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define INF 0x7fffffff
using namespace std;
void fff(){
    freopen("blockade.in","r",stdin);
    freopen("blockade.out","w",stdout);
}
const int MAXN=51000;
int n,m;
int a[MAXN];
int fa[MAXN],dist[MAXN],q[MAXN],top[MAXN],tdis[MAXN];
struct Edge{
    int from,to,val;
};
vector <Edge> edge;
vector <int> G[MAXN];
struct Army{
    int rest,top;//rest是多余的时间,top是原来所在的根节点
}army[MAXN];//这个是记录到达根节点剩下能够多走的军队
bool visited[MAXN],lea[MAXN];
bool cmp(int a1,int b1){
    return a1>b1;
}
bool cmp1(Army a1,Army b1){
    return a1.rest<b1.rest;
}
bool cmp2(Army a1,Army b1){
    return a1.rest>b1.rest;
}
////////////////////////以上是一些朴实的变量和判定
void dfs(int father){//朴实建树,求出相邻的父亲和位置
    int siz=G[father].size();
    for (int i=0;i<siz;i++){
        Edge &e=edge[G[father][i]];
        if(fa[e.to]==-1){
            fa[e.to]=father;
            dist[e.to]=e.val;
            dfs(e.to);
        }
    }
}
int st[MAXN][21],ffa[MAXN][21];
void st_set(){//朴实的倍增 
    for (int i=1;i<=n;i++){
        st[i][0]=dist[i];
        ffa[i][0]=fa[i];
    }
    for (int j=1;j<=20;j++){
        for (int i=1;i<=n;i++){
            ffa[i][j]=ffa[ffa[i][j-1]][j-1];
            st[i][j]=st[i][j-1]+st[ffa[i][j-1]][j-1];
        }
    }
}
void dfs1(int u,int father,int topf,int dist){//找到叶子结点和所处在的最高父亲(不越过根)
    top[u]=topf;
    tdis[u]=dist;
    bool ft=false;
    int siz=G[u].size();
    for (int i=0;i<siz;i++){
        Edge &e=edge[G[u][i]];
        int v=e.to;
        if(v==father) continue;
        ft=true;
        dfs1(v,u,topf,dist);
    }
    if(!ft) lea[u]=true;//如果没有子节点那他就是叶子
}

bool fs;
void dfs2(int u,int father){//进行深搜判断是否边界节点被覆盖 
    if(lea[u]){//OK这个节点还没有被覆盖
        fs=true;
        return;
    }
    int siz=G[u].size();
    for (int i=0;i<siz;i++){
        Edge &e=edge[G[u][i]];
        int v=e.to;
        if(v==father||visited[v]) continue;
        dfs2(v,u);
        if(fs) return;
    }
}
bool look(int v){
    fs=false;
    dfs2(v,ffa[v][0]);
    return fs;
}


int rrnum,tail;
bool query(int x){
    rrnum=0,tail=0;
    memset(visited,false,sizeof(visited));
    memset(q,0,sizeof(q));
    memset(army,0,sizeof(army));
    for (int i=1;i<=m;i++){
        int tim=x;
        int now=a[i];
        bool syst=false;
        while (true){
            for (int j=20;j>=0;j--){
                if(ffa[now][j]&&st[now][j]<=tim){//类似lca,使劲往上蹦跶
                    tim-=st[now][j];
                    now=ffa[now][j];
                    break;
                }
                if(j==0||now==1){
                    syst=true;//到了根节点或者到一杯父亲
                    break;
                }
            }
            if(syst) break;
        }
        if(now==1){//现在蹦到1的军队进行排队,计入原来的位置的最高父节点和剩下的时间 
            army[++rrnum].top=top[a[i]];
            army[rrnum].rest=tim;
        }else{
            visited[now]=true;//没有蹦到就原地不动,这个点打上标记 
        }
    }
    sort(army+1,army+m+1,cmp1);
    for (int i=1;i<=m;i++){
        if(army[i].rest<tdis[army[i].top]){//不能够走到根节点
            if(!visited[army[i].top]&&look(army[i].top)){
                visited[army[i].top]=true;//这个军队保持原地
                army[i].rest=-1;//把这个军队划掉 
            }
        }
    }
    sort(army+1,army+m+1,cmp2);//剩下的点再来排队 
    int siz=G[1].size();
    for (int i=0;i<siz;i++){
        Edge &e=edge[G[1][i]];
        int v=e.to;
        if(!visited[v]&&look(v)){
            q[++tail]=e.val;//如果需要军队的节点进入队列
        }
    }
    sort(q+1,q+tail+1,cmp);
    for(int i=1;i<=tail;i++){
        if(army[i].rest<q[i])//依次满足
        return false;
    }
    return true;
}
int L,R;
int main(){
//  fff();
    scanf("%d",&n);
    memset(fa,-1,sizeof(fa));
    L=R=0;
    for (int i=1;i<n;i++){
        int f,t,w;
        scanf("%d%d%d",&f,&t,&w);
        R+=w;
        edge.push_back((Edge){f,t,w});
        G[f].push_back(edge.size()-1);
        edge.push_back((Edge){t,f,w});
        G[t].push_back(edge.size()-1);
    }
    scanf("%d",&m);
    for (int i=1;i<=m;i++){
        scanf("%d",&a[i]);
    }
    if(G[1].size()>m){//其实比较简单的原理,如果所有军队都不能封住根的出度,那就肯定抑制不了
        cout<<-1;
        return 0;
    }
    fa[1]=0;
    dfs(1);
    for(int i=0;i<G[1].size();i++){
        Edge &e=edge[G[1][i]];
        int v=e.to;
        dfs1(v,1,v,e.val);
    } 
    st_set();
    int ans=0;
    L=0;
    while (L<=R){//二分枚举时间
        int mid=(L+R)>>1;
        if(query(mid)){
            ans=mid;
            R=mid-1;
        }else{
            L=mid+1;
        }
    }
    cout<<ans;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/80942466