noip2019集训测试赛(七)

Problem A: Maze

Time Limit: 1000 ms Memory Limit: 256 MB

Description

考虑一个N×M的网格,每个网格要么是空的,要么是障碍物。整个网格四周都是墙壁(即第1行和第n行,第1列和第m列都是墙壁),墙壁有且仅有两处开口,分别代表起点和终点。起点总是在网格左边,终点总是在网格右边。你只能朝4个方向移动:上下左右。数据保证从起点到终点至少有一条路径。

从起点到终点可能有很多条路径,请找出有多少个网格是所有路径的必经网格。

Input

第一行包含两个整数 N,M,表示网格 N 行 M列。

接下来 N行,每行 M个字符,表示网格。‘#‘表示障碍物或墙壁,‘.‘表示空地。

Output

输出文件包含一个整数,必经点的个数。

Sample Input
7 7
#######
....#.#
#.#.###
#.....#
###.#.#
#.#....
#######

Sample Output
5

HINT

样例解释

(2, 1) (2, 2) (4, 4) (6, 6) (6, 7)

数据范围与约定

对于10%的数据, 3≤N,M≤50

对于50%的数据, 3≤N,M≤500

对于所有数据, 3≤N,M≤1000

Solution

先建个图,然后tarjan割点

割点的时候判断这个点在不在起点到终点的路上,如果不在就没必要算入答案。

#include<bits/stdc++.h>
using namespace std;
struct qwq{
    int v;
    int nxt;
}edge[4000001];
int head[1000001];
int cnt=-1;
void add(int u,int v){
    edge[++cnt].nxt=head[u];
    edge[cnt].v=v;
    head[u]=cnt;
}
int dfn[1000001];
int low[1000001];
int rt;
int ind;
int s,t;
bool pd[1000001];
bool tarjan(int u){
    dfn[u]=low[u]=++ind;
    int child=0;
    bool flag=false;
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        bool fflag=false;
        if(!dfn[v]){
            fflag=tarjan(v);
            flag=flag||fflag;
            low[u]=min(low[u],low[v]);
            if(dfn[u]<=low[v]&&fflag){
                pd[u]=true;
            }
        }
        low[u]=min(low[u],dfn[v]);
    }
    return flag||u==t;
}
bool mapn[1001][1001];
int movex[4]={0,1,0,-1};
int movey[4]={1,0,-1,0};
int main(){
    memset(head,-1,sizeof(head));
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            char ch;
            cin>>ch;
            if(ch=='.'){
                mapn[i][j]=true;
                if(j==1){
                    s=(i-1)*n+j;
                }
                if(j==n){
                    t=(i-1)*n+j;
                }
            }
        }
    }
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(!mapn[i][j])continue;
            for(int k=0;k<4;++k){
                int x=i+movex[k],y=j+movey[k];
                if(x<1||y<1||x>n||y>m||!mapn[x][y])continue;
                add((i-1)*n+j,(x-1)*n+y);
            }
        }
    }
    //cout<<s<<" "<<t<<endl;
    tarjan(s);
    int ans=0;
    for(int i=1;i<=n*m;++i){
        if(pd[i]){
            ans++;
            //cout<<i<<endl;
        }
    }
    printf("%d\n",ans+1);
}

Problem B: 懒人跑步

Time Limit: 1000 ms Memory Limit: 256 MB

Description

在ZJU,每个学生都被要求课外跑步,并且需要跑够一定的距离 K,否则体育课会挂科。

ZJU有4个打卡点,分别标记为 p1,p2,p3,p4。每次你到达一个打卡点,你只需要刷一下卡,系统会自动计算这个打卡点和上一个打卡点的距离,并将它计入你的已跑距离。

系统把这4个打卡点看成一个环。 p1与 p2 相邻、 p2 与 p3 相邻、 p3 与 p4 相邻、 p4 与 p1 相邻。当你到达打卡点 pi时,你只能跑到与该打卡点相邻的打卡点打卡。

打卡点 p2是离宿舍最近的一个打卡点。CJB总是从 p2 出发,并回到 p2 。因为CJB很圆,所以他希望他跑的距离不少于 K,但又要尽量小。

Input

第一行为一个整数 T,表示数据组数。

对于每组数据,有5个正整数 K,d1,2,d2,3,d3,4,d4,1(1≤K≤10^18,1≤d≤30000),表示至少要跑的距离和每两个相邻的打卡点的距离。

Output

对于每组数据,输出一个整数表示CJB最少需要跑多少距离。

Sample Input

1
2000 600 650 535 380

Sample Output

2165

HINT

样例解释

最优路径为 2?1?4?3?2

数据范围与约定

对于30%的数据, 1≤K≤30000,1≤d≤30000

对于100%的数据, 1≤K≤10^18,1≤d≤30000,1≤T≤10

Solution

首先我们显然可以在任意一条道路上来回摩擦

那么假设我们有一条长度为K的路径,设w=min(dis(1,2),dis(2,3)),肯定有一条长度为k+2w的路径

所以我们设dis[i][j]为到达某一个点,且dis[i][j]≡j(mod 2w)的最短距离

然后用类似最短路的方式更新,最后到达2号点的mod 2w的值最小的路径的就好了

#include<bits/stdc++.h>
using namespace std;
#define ll long long
struct data{
    int p;
    int m;
};
int d[4];
ll dis[4][100001];
bool vis[4][100001];
void spfa(int w){
    memset(dis,0x7f,sizeof(dis));
    //memset(vis,0,sizeof(vis));
    queue<data> q;
    q.push(data{1,0});
    dis[1][0]=0;
    vis[1][0]=true;
    while(!q.empty()){
        int p=q.front().p,m=q.front().m;
        int nxt=(p+1)%4,pre=(p+3)%4;
        //cout<<p<<" "<<m<<" "<<nxt<<" "<<pre<<endl;
        q.pop();
        vis[p][m]=false;
        if(dis[p][m]+d[p]<dis[nxt][(m+d[p])%w]){
            dis[nxt][(m+d[p])%w]=dis[p][m]+d[p];
            if(!vis[nxt][(m+d[p])%w]){
                q.push(data({nxt,(m+d[p])%w}));
                vis[nxt][(m+d[p])%w]=true;
            }
        }
        if(dis[p][m]+d[pre]<dis[pre][(m+d[pre])%w]){
            dis[pre][(m+d[pre])%w]=dis[p][m]+d[pre];
            if(!vis[pre][(m+d[pre])%w]){
                q.push(data{pre,(m+d[pre])%w});
                vis[pre][(m+d[pre])%w]=true;
            }
        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ll k;
        scanf("%lld%d%d%d%d",&k,&d[0],&d[1],&d[2],&d[3]);
        int w=min(d[0],d[1]);
        spfa(w*2);
        while(dis[1][k%(w*2)]>k)k++;
        printf("%lld\n",k);
    }
}

Problem C: 道路建设

Time Limit: 4000 ms Memory Limit: 512 MB

Sample Input

5 7
1 2 2
2 3 4
3 4 3
4 5 1
5 1 3
2 5 4
1 4 5
5
1 2
4 7
11 12
11 13
18 19

Sample Output

3
9
8
14
13

HINT

样例解释

解密后的询问为 (1,2),(1,4),(2,3),(3,5),(4,5)

修建道路最小费用的方案为 {(1,2),(4,5)},{(2,1),(1,5),(5,4),(4,3)},{(1,2),(1,5),(3,4)},{(1,5),(5,2),(2,3),(3,4)},{(3,2),(2,5),(1,4)}

数据规模与约定

子任务1(5分): 1≤n,m,q≤1000,online=1

子任务2(11分): 1≤n≤1000,1≤m,q≤10^5,online=0

子任务3(14分): 1≤n≤1000,1≤m,q≤10^5,online=1

子任务4(21分): 1≤n,m,q≤10^5,online=0

子任务5(49分): 1≤n,m,q≤10^5,online=1

Solution

首先如果此题没有强制在线,我们可以用LCT模拟建立最小生成树的过程。

首先把边权从大到小排序,不断把边插入LCT中,

如果当前加入的边与原来的边构成了一个环,我们找到这个环上最大的边去掉,然后加入这条边。

现在我们要让他能够在线处理,那我们就建立一棵主席树来方便查询历史版本。

然后每次查询l,r只需要查询版本为l且小于等于r的边的和就可以了(因为在这个版本中比l小的还未加入进来)

有史以来写过的最恶心的题

#include<bits/stdc++.h>
using namespace std;
struct node{
    int ch[2];
    int fa;
    int val;
    int tag;
    int mp;
}t[300001];
bool nroot(int x){
    return t[t[x].fa].ch[0]==x||t[t[x].fa].ch[1]==x;
}
void pushup(int x){
    t[x].mp=x;
    int lc=t[x].ch[0],rc=t[x].ch[1];
    if(t[t[lc].mp].val>t[t[x].mp].val)t[x].mp=t[lc].mp;
    if(t[t[rc].mp].val>t[t[x].mp].val)t[x].mp=t[rc].mp;
}
void rev(int x){
    swap(t[x].ch[0],t[x].ch[1]);
    t[x].tag^=1;
}
void pushdown(int x){
    if(t[x].tag){
        if(t[x].ch[0])rev(t[x].ch[0]);
        if(t[x].ch[1])rev(t[x].ch[1]);
        t[x].tag=0;
    }
}
void rotate(int x){
    int fa=t[x].fa;
    int gfa=t[fa].fa;
    bool k=t[fa].ch[1]==x;
    if(nroot(fa))t[gfa].ch[t[gfa].ch[1]==fa]=x;
    t[x].fa=gfa;
    t[fa].ch[k]=t[x].ch[k^1];
    if(t[x].ch[k^1])t[t[x].ch[k^1]].fa=fa;
    t[fa].fa=x;
    t[x].ch[k^1]=fa;
    pushup(fa);pushup(x);
}
int st[2000001];
void splay(int x){
    int y=x,z=0;
    st[++z]=y;
    while(nroot(y)){
        st[++z]=y=t[y].fa;
    }
    while(z)pushdown(st[z--]);
    while(nroot(x)){
        int fa=t[x].fa;
        int gfa=t[fa].fa;
        if(nroot(fa)){
            if((t[fa].ch[1]==x)^(t[gfa].ch[1]==fa))rotate(x);
            else rotate(fa);
        }
        rotate(x);
    }
    pushup(x);
}
void access(int x){
    int y=0;
    while(x){
        //cout<<y<<" "<<x<<" "<<t[x].fa<<endl;
        splay(x);
        t[x].ch[1]=y;
        pushup(x);
        y=x;
        x=t[x].fa;
    }
}
void makeroot(int x){
    access(x);
    splay(x);
    rev(x);
}
void link(int x,int y){
    makeroot(x);
    t[x].fa=y;
}
int cutmax(int x,int y){
    makeroot(x);
    access(y);
    splay(y);
    x=t[y].mp;
    splay(x);
    t[t[x].ch[0]].fa=t[t[x].ch[1]].fa=0;
    t[x].ch[0]=t[x].ch[1]=0;
    return x;
}
struct qwq{
    int u,v,w;
}edge[1000001];
bool operator <(qwq a,qwq b){
    return a.w>b.w;
}
int fa[100001];
int findfa(int x){
    return fa[x]==x?x:fa[x]=findfa(fa[x]);
}
struct seg{
    int l,r,val;
}tt[4000001];
int rt[10001];
int cnt;
void update(int now,int &root,int p,int v,int l,int r){
    root=++cnt;
    tt[root]=tt[now];
    tt[root].val+=v;
    if(l==r)return;
    int mid=(l+r)/2;
    if(p<=mid)update(tt[now].l,tt[root].l,p,v,l,mid);
    else update(tt[now].r,tt[root].r,p,v,mid+1,r);
}
int query(int now,int L,int R,int l,int r){
    if(now==0)return 0;
    if(L<=l&&r<=R)return tt[now].val;
    int mid=(l+r)/2;
    int ret=0;
    if(L<=mid)ret+=query(tt[now].l,L,R,l,mid);
    if(mid<R)ret+=query(tt[now].r,L,R,mid+1,r);
    return ret;
}
int main(){
    int n,m,online;
    scanf("%d%d%d",&n,&m,&online);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    }
    sort(edge+1,edge+1+m);
    for(int i=1;i<=n;++i)fa[i]=i;
    int N=edge[1].w;
    for(int i=1;i<=m;++i){
        rt[edge[i].w]=rt[edge[i-1].w];
        int u=edge[i].u,v=edge[i].v;
        int x=findfa(u),y=findfa(v);
        int q;
        if(x==y){
            q=cutmax(u,v);
            update(rt[edge[i].w],rt[edge[i].w],t[q].val,-t[q].val,1,N);
        }
        else {
            fa[x]=y;
            q=i+n;
        }
        t[q].val=edge[i].w;
        t[q].mp=q;
        link(u,q);
        link(q,v);
        update(rt[edge[i].w],rt[edge[i].w],edge[i].w,edge[i].w,1,N);
    }
    for(int i=N;i>=2;i--){
        if(!rt[i-1]){
            rt[i-1]=rt[i];
        }
    }
    int q,last=0;
    scanf("%d",&q);
    while(q--){
        int l,r;
        scanf("%d%d",&l,&r);
        l-=last*online;
        r-=last*online;
        printf("%d\n",last=query(rt[l],1,r,1,N));
    }
}

原文地址:https://www.cnblogs.com/youddjxd/p/11371946.html

时间: 2024-10-13 00:14:25

noip2019集训测试赛(七)的相关文章

noip2019集训测试赛(二)

Problem A: 余数 Time Limit: 1000 ms Memory Limit: 512 MB Description Input Output Sample Input 3 4 Sample Output 4 HINT Solution 那个所谓\(\sqrt n\)的东西叫做整除分块. 显然对于\(n÷i\),当$i<=\sqrt n $时,每个i都有一种取法 当\(n>=i>\sqrt n\),i每加\(\sqrt n\),\(n÷i\)的值就+1 然后就可以根号时间

noip2019集训测试赛(四)

Problem A: fibonacci Time Limit: 3000 ms Memory Limit: 256 MB Description Input 第一行两个数 N,M . 第二行 N 个数 a1,a2,...,an . 接下来 M 行, 每行代表题目描述中的一种操作. Output 对于每个询问, 输出一行, 表示答案. Sample Input 5 4 1 1 2 1 1 2 1 5 1 2 4 2 2 2 4 2 1 5 Sample Output 5 7 9 HINT 对于

[补档]noip2019集训测试赛(十五)

Problem A: 传送带 Time Limit: 1000 ms Memory Limit: 256 MB Description 在一个二维平面上有两条传送带,每一条传送带可以看成是一条线段.两条传送带分别为线段AB和线段CD.小y在AB上的移动速度为P,在CD上的移动速度为Q,在平面上的移动速度R.现在,小y想从A点走到D点,请问他最少需要走多长时间. Input 第一行是4个整数,表示A和B的坐标,分别为Ax,Ay,Bx,By. 第二行是4个整数,表示C和D的坐标,分别为Cx,Cy,D

[补档]noip2019集训测试赛(十二)

Problem A: 记忆(memory) Time Limit: 1000 ms Memory Limit: 512 MB Description 你在跟朋友玩一个记忆游戏. 朋友首先给你看了n个长度相同的串,然后从中等概率随机选择了一个串. 每一轮你可以询问一个位置上的正确字符,如果能够凭借已有的信息确定出朋友所选的串,那么游戏就结束了,你的成绩就是所用的轮数. 由于你实在太笨,不会任何策略,因此你采用一种方法,每次等概率随机询问一个未询问过的位置的字符. 现在你想知道,在这种情况下,你猜出

[补档]noip2019集训测试赛(八)

Problem B: 2048 Special Judge Time Limit: 1000 ms Memory Limit: 256 MB Description 2048曾经是一款风靡全球的小游戏. 今天,我们换一种方式来玩这个小游戏. 现在,你有一个双端队列,你只能把元素从左端或从右端放入双端队列中.一旦放入就不得取出.放入后,若队列中有连续两个相同的元素,它们将自动合并变成一个新的元素--原来那两个元素的和.若新的元素与它相邻的元素相同,则继续合并-- 如:双端队列中有2, 4, 16三

【2016北京集训测试赛(八)】 crash的数列

Description 题解 题目说这是一个具有神奇特性的数列!这句话是非常有用的因为我们发现,如果套着这个数列的定义再从原数列引出一个新数列,它居然还是一样的...... 于是我们就想到了能不能用多点数列套着来加速转移呢? 但是发现好像太多数列套起来是可以烦死人的...... 我们就采用嵌套两次吧,记原数列为A,第一层嵌套为B,第二层嵌套为C. 我们其实可以发现一些规律,对于Ci,它对应了B中i的个数:对于Bi,它对应了A中i的个数. 稍加处理即可,我们一边计算一边模拟数列的运算,同时可以计算

2016集训测试赛(二十五)小结

这场测试赛有必要小结一下. 昨晚 1 点才睡, 今天状态很差, 先睡了 1 个小时, 然后开始打比赛. 第一题不大会做, 写了一个代码后发现是错的, 第二题看不懂题, 第三题简单地分析了一下, 发现是一个树形DP . 然后做 T3 , 大概推了很久, 写了很久, 又写了几个对拍, 搞到 11 点才搞掂. 这时候我发现 T1 有 50 分是我可做的, 然后 T2 的题意仍然不是很明确, 我想尝试着写写 T2 , 这个必须要写出来才能看出题意是不是这样, 写着写着发现 T2 的题意不是我理解的这样,

【2016北京集训测试赛(七)】自动机 (思考题)

Time Limit: 1000 ms Memory Limit: 256 MB Description Solution 这是一道看起来令人毫无头绪的题,然而确实十分简单巧妙TAT. 题目要求所有点执行相同指令后都回到初始状态. 我们先来考虑只有两种状态的情况:初始状态$T_0$与另一个状态$T_x$. 这样,我们可以通过一个二元记忆化深搜,来得到一种方案A,使得$T_0$回到$T_0$,且$T_x$回到$T_0$.如果这个方案都不存在,那么此时无解. 现在我们知道,执行方案A后,$T_x$与

[北京集训测试赛(三)]灯(Light)-奇怪乱搞数学题-素数

Problem 灯 题目大意 n盏灯排成一列,标号一到n,一开始标号为1的灯亮着. 现在依次对于2~n的每一个质数pi,指定一盏亮着的灯ai,点亮所有标号为$A[i]\pm kP_i$的灯. 有spj,任意一种方案即可. 输入一个整数n,输出点灯方案. Solution 首先写个暴力,考虑一下小范围的数据. 我们发现$n<16$的时候没有完美解,都是n-1.再算下去,发现$n>16$的时候任意一组都有完美解. 我们分析一下这个玩意儿. 把一到n的灯集体下标前移1,变成0~n-1.这时候当我们点