Training Day2

这篇博客记录了ACM集训第二天的训练内容,涉及多个算法题目,包括Tarjan LCA、数学问题、找规律、反转开关问题、双指针、动态规划、概率DP和计算几何等。每道题目都详细分析了解题思路并提供了代码实现。
摘要由CSDN通过智能技术生成

马克飞象莫名其妙无法同步到印象笔记,现在这里保存一下吧。


Training Day2

@(ACM集训)

A.How far away ?(Tarjan LCA)

题意:

无向图,给定边及边权重,任意两点之间都有一条唯一的道路,道路上每个点只能出现一次。给定询问,求询问的结点之间的距离。

分析:

路上每个点只能出现一次,可以转化成有根树,问题也即为求最近公共祖先问题~~ 这里每条边加上了距离,求出LCA后,用 uv 的距离根的距离和减去 2 倍的根到最近公共祖先的距离即可,然后就是tarjan LCA 模板题了。

代码:

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
#define fuck cout<<"fuck"<<endl;
const int maxn = 40005, maxq = 205;
typedef pair<int, int>p;
vector<p>G[maxn];
#define fi first
#define se second
int pa[maxn];
bool vis[maxn];
bool in[maxn];
int ance[maxn];
int dist[maxn];
int n, tot;
int head[maxn];
int ans[maxn];
struct Query{int to, next, index;};
Query query[maxq * 2];
void add_query(int u, int v, int index)
{
    query[tot].to = v;
    query[tot].index = index;
    query[tot].next = head[u];
    head[u] = tot++;
    query[tot].to = u;
    query[tot].index = index;
    query[tot].next = head[v];
    head[v] = tot++;
}
int _find(int x)
{
    if(pa[x] != x) return pa[x] = _find(pa[x]);
    return x;
}
void unite(int x, int y)
{
    int rx = _find(x), ry = _find(y);
    if(rx == ry) return;
    pa[rx] = ry;
}
void init()
{
    tot = 0;
   for(int i = 1; i <= n; i++){
        G[i].clear();
        pa[i] = i;
   }
    mem(ance, 0);
    mem(vis, false);
    mem(head, -1);
    mem(dist, 0);
    mem(in, false);
    mem(ans, 0);
}
void LCA(int u)
{
    ance[u] = u;
    vis[u] = true;
    for(int i = 0; i < G[u].size(); i++){
        int v = G[u][i].fi;
        if(vis[v]) continue;
        dist[v] = dist[u] + G[u][i].se;
        LCA(v);
        unite(u, v);
        ance[_find(u)] = u;
    }

    for(int i = head[u]; i != -1; i = query[i].next){
        int v = query[i].to;
        if(vis[v])  ans[query[i].index] = dist[u] + dist[v] - 2 * dist[ance[_find(u)]];
    }
}
int main(void)
{
    int u, v, k;
    int a, b, c;
    int Q;
    int T;scanf("%d", &T);
    while(T--){
        init();
        scanf("%d%d", &n, &Q);
        for(int i = 0; i < n - 1; i++){
            scanf("%d%d%d",&a, &b, &c);
            G[a].push_back(p(b, c));
            G[b].push_back(p(a, c));
        }
        for(int i = 0; i < Q; i++){
           scanf("%d%d",&u,&v);
            add_query(u, v, i);
        }
        dist[1] = 0;
        LCA(1);
        for(int i = 0; i <Q; i++){
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

E.Crossing River(数学)

题意:

经典过河问题,一个船只能运两个人,每个人过河时间不同,船来回往返使得 n 个人全部通过,问如何安排使得总时间最小。

分析:

小学奥数题的感觉。。
决策如下:
1. n<=2 ,直接过河。
2. n=3 ,让最快的往返一次。
3. n>=4 ,要么最快的来回往返,要么最慢的和次慢的过去了就不要再回来。那么把最慢的和次慢的两个人送过去就有两种方法:
- 最小的来回往返,一次送最慢,回来,再送次慢。
- 最小和次小的一起送(要保证最慢的和次慢的过去以后还有人能把船运回来),最小的和次小的先一起过去,然后最小的回来,然后最慢的和次慢的一起过去,次小的回来。

取两种方法中的最小值即可。依次循环按上述策略处理。

代码:
/*************************************************************************
    > File Name:
    > Author: jiangyuzhu
    > Mail: 834138558@qq.com
 ************************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
typedef pair<int, int>p;
typedef long long ll;
const int maxn = 1e3 + 5;
int a[maxn];
int main (void)
{
    int T;cin>>T;
    while(T--){
        int n;cin>>n;
        for(int i = 0; i < n; i++) cin>>a[i];
        sort(a, a + n);
        int ans = 0;
        while(n){
            if(n == 1){
                ans += a[0];
                break;
            } else if(n == 2){
                ans += a[1];
                break;
            }else if(n == 3){
                ans += a[0] + a[1] + a[2];
                break;
            }else{
                int t1 = a[0] * 2 + a[n - 1] + a[n - 2];
                int t2 = a[1] * 2 + a[0] + a[n - 1];
                n -= 2;
                ans += min(t1, t2);
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

G.Piotr’s Ants(找规律)

题意:

给定 n 个蚂蚁的初始位置及方向,两只蚂蚁相遇后同时掉头,速度均为 1m/s ,问 T 时间后,各个蚂蚁的位置。

分析:

可以将问题看成,两只蚂蚁相遇后交换速度和方向,那么本质就相当于相遇后直接穿过对方,继续原方向行走,而由于两只蚂蚁相遇后直接掉头,所以最后 所有蚂蚁的相对顺序保持不变,这样我们算出所有终态,排个序,按照对应初始位置的顺序输出即可。注意判断是否掉下去的优先级要比位置是否相同的高!

代码:

/*************************************************************************
    > Author: jiangyuzhu
    > Mail: 834138558@qq.com
 ************************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 1e4 + 5, oo = 0x3f3f3f3f;
struct NODE{int x; int dir; int id;};
NODE node[maxn], nnode[maxn];
map<int, int>MAP;
bool cmp(NODE a, NODE b)
{
    return a.x < b.x;
}
int main (void)
{
    int N;sa(N);
    for(int kas = 1; kas <= N; kas++){
        int L, T, n;sa(L);sa(T);sa(n);
        int x;
        for(int i = 1; i <= n; i++){
            sa(x);getchar();
            if(getchar() == 'L'){
                node[i] = (NODE){x, 1, i};
                nnode[i] = (NODE){x - T, 1, 0};
            }else{
                node[i] = (NODE){x, 2, i};
                nnode[i] = (NODE){x + T, 2, 0};
            }
        }
        sort(node + 1, node + n + 1, cmp);
        for(int i = 1; i <= n; i++){
           MAP[node[i].id] = i;
        }
        sort(nnode + 1, nnode + n + 1, cmp);
        for(int i = 1; i <= n; i++){
            if(nnode[i].x < 0 || nnode[i].x > L)
                nnode[i].dir = -1;
            else if(i < n && nnode[i].x == nnode[i + 1].x){
                nnode[i].dir = 0;
                nnode[i + 1].dir = 0;
            }
        }
        printf("Case #%d:\n", kas);
        for(int j = 1; j <= n; j++){
            int i = MAP[j];
            if(nnode[i].dir == -1) {
                cout<<"Fell off"<<endl;
                continue;
            }
            cout<<nnode[i].x<<' ';
            if(nnode[i].dir == 1){
                cout<<'L'<<endl;
            }else if(nnode[i].dir == 2){
                cout<<'R'<<endl;
            }else if(nnode[i].dir == 0){
                cout<<"Turning"<<endl;
            }
        }
        cout<<endl;
        /*for(int i = 1; i <= n; i++){
            cout<<nnode[i].id<<' '<<nnode[i].x<<' '<<nnode[i].dir<<endl;
        }*/
    }
    return 0;
}

I.The Water Bowls(反转开关问题)

题意:

给定01序列,翻转一位其左右相邻两位也会被翻转,问使序列全部变为0的最小翻转次数。

分析:

简单线性排列,由于后面是否翻转由前面的决定,所以我们枚举第一个元素的状态,然后从头扫一遍即可。

代码:

/*************************************************************************
    > Author: jiangyuzhu
    > Mail: 834138558@qq.com
 ************************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pr(x) cout<<#x<<":"<<x
#define pl(x) cout<<#x<<":"<<x<<endl
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%I64d", &(n))
typedef long long ll;
const int maxn = 20 + 5, oo = 0x3f3f3f3f;
int f[maxn], a[maxn];
int main (void)
{
    for(int i = 0; i < 20; i++){
        cin>>a[i];
    }
    memset(f, 0, sizeof(f));
    int cnt = 0;
    for(int i = 1; i < 20; i++){
        if((a[i - 1] + f[i - 1]) & 1){
            cnt++;
             f[i]++;
             f[i + 1]++;
        }
    }
    memset(f, 0, sizeof(f));
    int cnt2 = 1;
    f[0] = f[1] = 1;
    for(int i = 1; i < 20; i++){
        if((a[i - 1] + f[i - 1]) & 1){
             cnt2++;
             f[i]++;
             f[i + 1]++;
        }
    }
    cout<<min(cnt, cnt2)<<endl;
    return 0;
}

J.Fliptile(反转开关问题)

题意:

mn 个黑白块,黑块的背面是白块,白块背面是黑块,一头牛踩一块,则这个块的上下左右的方块都会转动,问至少踩多少块,才会使所有块都变成白色?

分析:

一个块的转动会影响其他块的状态,这里不是简单的线性排列,不能只踩黑块。
首先根据字典序,我们可以对第一排从 0000 11..11 进行考虑(1表示踩),再后续一排一排的考虑。因为踩一块四周的块都会转动,所以必须规定个踩的原则,发现对于某块来说,他一旦改变上方块的状态,那个块就再也不会改变了,而其他块还有他的下一列改变他的机会(如果存在),所以就以上一行块为原则,如果上方为黑,则踩。最后判断最后一行是否全白。
字典序,因为他说了是把整个排列当做字符串的字典序,所以肯定是越前面的越小越好,而且从第一个例子中也能看出来。

代码:
#include<iostream>
#include<cstring>
using namespace std;
#define mem(s,a)  memset(s,a,sizeof(s));
int m, n;
const int maxn = 25, INF = 0x3fffffff;
int x[5]={-1,0,0,0,1};
int  y[5] = {
  0,1,0,-1,0};
int s[maxn][maxn], a[maxn][maxn], r[maxn][maxn], ans[maxn][maxn];
int cal()
{
    int cnt = 0;
    for(int i = 2; i <= m; i++){
        for(int j = 1; j <= n; j++){
            if((a[i-1][j] + r[i-1][j]) % 2 == 1) {
                cnt++;
                s[i][j] = 1;
            }
            for(int k = 0; k < 5; k++){
                r[i + x[k]][j + y[k]] += s[i][j];
            }
        }
    }
    for(int i =1; i <= n; i++){
        if((r[m][i] + a[m][i]) % 2 == 1) return -1;
    }

    return cnt;
}
int main (void)
{
    cin>>m>>n;
    for(int i = 1; i <= m; i++){
        for(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值