2019-2020 ICPC North-Western Russia Regional Contest【部分题解】

题目:

ICPC 2019-2020 North-Western Russia Regional Contest

E:Equidistant

题意:

给定一颗树,再给出 m 个关键点,让你找出一个点使得这个点到 m 个关键点的距离相等

分析:

(1)直观的做法就是换根dp,预处理出每个节点为根时,子树中的关键点到它距离最大值和最小值,次最大值和次最小值,换根dp的时候直接看所有关键点到根的最大值和最小值是否相等

(2)问题可以转换为找到一个点,使得所有关键点到它距离的最大值最小,这个点一定是关键点的直径上的中点,这个点一定是唯一的,先两边 dfs 找到直径,再找到中点,最后判断一下这个中点是否满足题意即可

代码:

#include 
 
#define f first
#define s second
#define pii pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 2e5+25;
int head[maxn],n,m,u,v,cnt,d[maxn],tag[maxn],p[maxn];
struct edge{
    int to,nxt;
}e[maxn<<1];
inline void add(int u,int v){
    e[++cnt] = (edge){v,head[u]};
    head[u] = cnt;
}
int x,len;
void dfs(int u,int fa,int step){
    if(tag[u]&&step>=len){
        x = u; len = step;
    }
    for(int i = head[u];i > 0;i = e[i].nxt){
        int v = e[i].to; if(v == fa) continue;
        dfs(v,u,step+1);
    }
}
bool flag;
int q[maxn],k;
void dfs2(int u,int fa){
    q[k++] = u;
    if(u == x){
        flag = true; return;
    }
    for(int i = head[u];i>0&&!flag;i = e[i].nxt){
        int v = e[i].to; if(v == fa) continue;
        dfs2(v,u); 
    }
    if(!flag) k--;
}
void dfs3(int u,int fa,int depth){
    d[u] = depth;
    for(int i = head[u];i > 0;i = e[i].nxt){
        int v = e[i].to; if(v == fa) continue;
        dfs3(v,u,depth+1);
    }
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1;i < n; ++i){
        scanf("%d %d",&u,&v);
        add(u,v); add(v,u);
    }
    for(int i = 1;i <= m; ++i){
        scanf("%d",p+i); tag[p[i]] = 1;
    }
    if(m == 1){
        puts("YES"); puts("1"); return 0;
    }
    dfs(p[1],0,0); int y = x; len = 0;
    dfs(y,0,0); dfs2(y,0);
    if(k % 2 == 0) return puts("NO");
    else k /= 2;
    dfs3(q[k],0,1);
    for(int i = 2;i <= m; ++i){
        if(d[p[i]] != d[p[i-1]]) return puts("NO");
    }
    puts("YES");
    cout << q[k] << '\n';
    return 0;
}

H:High Load Database

题意:

给定 N 个数,让你将这些数分成连续的块,每一块的和 <= t,求最少可以分成多少块

分析:

知道 t 的值,可以O(N)贪心的求得最少的块数,但现在有多个 t,那么就不能直接扫一遍,考虑优化一下;对原数组求一遍前缀和,那么每次就可以二分得到下一块边界的位置,那么这样复杂度的上限就是总的块数*logN;对于每一个 t,它的块数在 N/t 左右,对于相同的 t 只求一遍,那么这就是一个调和级数了;复杂度O(NlogNlogN)

想了半天才发现(太菜了)

代码:

#include 
 
#define f first
#define s second
#define pii pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 1e6+15;
const int mod = 1e9+7;
int n,q,t,m,a[maxn],sum[maxn],ans[maxn];
int solve(int t){
    int res = 0,pos = 0,num;
    while(pos < n){
        num = sum[pos]+t;
        pos = lower_bound(sum+pos+1,sum+n+1,num)-sum;
        if(sum[pos] > num) pos--;
        res++;
    }
    return res;
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n; ++i){
        scanf("%d",a+i); m = max(m,a[i]);
        sum[i] = sum[i-1] + a[i];
    }
    scanf("%d",&q);
    while(q--){
        scanf("%d",&t);
        if(t < m) puts("Impossible");
        else if(ans[t]) printf("%d\n",ans[t]);
        else printf("%d\n",ans[t] = solve(t));
    }
    return 0;
}

I:Ideal Pyramid

题意:

给定 N 个柱子的坐标和高度,让你找到一个最小的四棱锥(斜面和底面夹角为45°),使得全部的柱子都在里面

分析:

由于四棱锥的斜面和底面夹角为45°,那么只要知道它的高度,它的形状就唯一确定了;那么就可以二分它的高度,考虑如何check,发现对于一个柱子要在它的里面,那么四棱锥的低面中心可以在这个柱子的周围为正方形的区域内,只要对 N 个正方形求一个交集即可

代码:

#include 
 
#define f first
#define s second
#define pii pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 2000;
struct point{
    LL x,y,h;
}p[maxn];
LL ansx,ansy,ans,n;
struct node{
    LL x,y,xx,yy;
};
node merge(node a,node b){
    return (node){max(a.x,b.x),max(a.y,b.y),min(a.xx,b.xx),min(a.yy,b.yy)};
}
bool check(LL mid){
    LL t = mid - p[1].h;
    node s = (node){p[1].x-t,p[1].y-t,p[1].x+t,p[1].y+t};
    for(int i = 2;i <= n; ++i){
        t = mid - p[i].h;
        s = merge(s,(node){p[i].x-t,p[i].y-t,p[i].x+t,p[i].y+t});
        if(s.xx < s.x || s.yy < s.y) return false;
    }
    ans = mid; ansx = s.x,ansy = s.y;
    return true;
}
int main(){
    cin >> n;
    LL L = 0, R = 1e18;
    for(int i = 1;i <= n; ++i){
        cin>>p[i].x>>p[i].y>>p[i].h;
        L = max(L,p[i].h);
    }
    while(L <= R){
        LL mid = (L+R)>>1;
        if(check(mid)) R = mid - 1;
        else L = mid + 1;
    }
    cout << ansx << " " << ansy << " " << ans << '\n';
    return 0;
}

J:Just the Last Digit

题意:

有一张有向无环图,给出每个点到另一个点的方案数的个位数,让你还原这张图

分析:

容易发现,s 到 e 的方案中要么直达,要么经过 k(s < k < e);为了不重复计算,只需要知道 s 能否直接到达 k ,能就加上 k到 e 的方案,最后判断是否需要直达即可

代码:

#include 
 
#define f first
#define s second
#define pii pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 625;
char a[maxn][maxn];
int n,mp[maxn][maxn];
int main(){
    cin >> n;
    for(int i = 1;i <= n; ++i) scanf("%s",a[i]+1);
    for(int s = 1;s <= n; ++s){
        for(int e = s+1;e <= n; ++e){
            int sum = 0;
            for(int k = s+1;k < e; ++k){
                if(mp[s][k]) sum += a[k][e]-'0';
            }
            if(sum%10 != a[s][e]-'0') mp[s][e] = 1;
        }
    }
    for(int i = 1;i <= n; ++i){
        for(int j = 1;j <= n; ++j)
            printf("%d",mp[i][j]);
        puts("");
    }
    return 0;
}

 

你可能感兴趣的:(CodeForces,----Gym)