20180219膜你赛题解

介于本场比赛题目难度并不按照难度顺序排列,所以题解顺序不同。

需要题面或数据可以私聊我QQ(不一定在):468170499.

T3:最短路(path)

来源:jzoj3470

Describe:

给定一个n个点m条边的有向图,有k个标记点,要求从规定的起点按任意顺序经过所有标记点到达规定的终点,问最短的距离是多少。

Hint:

20%的数据n<=10。

50%的数据n<=1000。

另有20%的数据k=0。

100%的数据n<=50000,m<=100000,0<=k<=10,1<=z<=5000。

解题思路:

我在考场上写这道题的时候,一眼没看出来。在最后看了一眼数据,发现k<=10,那么答案就非常显然了。我们可以把标记点为起点跑一边最短路。我感觉可能会卡SPFA(然而出题人很凉心),所以就写了一个堆优化的Dijkstra。时间复杂度为O(log(N + M) * N * K)(我习惯变量用小写)。因为k很小,所以我们可以枚举所有的方案,再跑一层k的循环判断。时间复杂度为(K! * K),勉强不会超时,可以写状态压缩优化成O(2^K * K * K),因为这道题暴力能过,所以就没写,以后有空再补吧。这道题我在考场上是只过了一个样例,只有九分。主要是没看清题,有向图连了无向边。还有dis数组因为不能开到n方的大小,所有有一维应该要存点的编号,然后我在这里好像弄错了一点什么。。。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 50500, M = 202000, K = 11;
int n, m, k, s, t;
int tot = 0, Link[M], Next[M], to[M];
ll w[M];

inline int read() {
    int num = 0; bool flag = 1; char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-') flag = 0;
    for (; c >= '0' && c <= '9'; c = getchar())
        num = (num << 3) + (num << 1) + c - 48;
    return flag ? num : -num;
}

inline ll readll() {
    ll num = 0; bool flag = 1; char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-') flag = 0;
    for (; c >= '0' && c <= '9'; c = getchar())
        num = (num << 3) + (num << 1) + c - 48;
    return flag ? num : -num;
}

inline void add(int x, int y, ll z) {
    Next[++ tot] = Link[x]; 
    Link[x] = tot; 
    to[tot] = y; 
    w[tot] = z;
}

// dis[i][j]表示从point[i]号节点为起点到j的最短距离 
ll minn = LLONG_MAX;
int ans[N], point[N];
bool use[K];
ll dis[K][N];
bool vis[K][N];
void dij(int start) {
    priority_queue < pair < ll, int> > q;
    for (int i = 1; i <= n; ++ i) dis[start][i] = 1e15, vis[start][i] = 0;
    dis[start][point[start]] = 0;
    q.push(make_pair(0, point[start]));
    for(; q.size(); ) {
        int x = q.top().second; q.pop();
        if (vis[start][x]) continue;
        vis[start][x] = 1;
        for(int i = Link[x]; i; i = Next[i]) {
            int y = to[i], z = w[i];
            if (dis[start][y] > dis[start][x] + z) {
                dis[start][y] = dis[start][x] + z;
                q.push(make_pair(-dis[start][y], y));
            }
        }
    }
}

void dfs(int depth) {
    if (depth == k + 1) {
        ll sum = 0;
        sum += dis[0][point[ans[1]]];
        for (int i = 1; i < k; ++ i) sum += dis[ans[i]][point[ans[i + 1]]];
        sum += dis[ans[k]][t];
        minn = min(minn, sum);
    }
    for (int i = 1; i <= k; ++ i) 
        if (!use[i]) {
            use[i] = 1; 
            ans[depth] = i; 
            dfs(depth + 1); 
            use[i] = 0;
        }
    
}

int main() {
  freopen("path.in", "r", stdin);
  freopen("path.out", "w", stdout);
    n = read(), m = read(), k = read(), s = read(), t = read();
    for (int i = 1; i <= m; ++ i) {
        int x = read(), y = read();
        ll z = readll();
        add(x, y, z);
    }
    point[0] = s;
    dij(0);
    for (int i = 1; i <= k; ++ i) {
        int sign = read();
        point[i] = sign;
        dij(i);
    }
    dfs(1);
    if (minn == 1e15) puts("-1");
    else printf("%lld\n", minn);
    return 0;
}

 T1:生成输入数据(input)

Describe:

首先看到题目别太开心,这题可不是让你出数据~^_*

背景神马的就忽略了。这题就是给你一棵带边权的树,然后这棵树是某个完全图唯一的最小生成树。问原来的完全图中所有边可能的最小边权和是多少。

完全图是任意两个点之间都有边相连的图。

Hint:

20%的数据满足:T≤5,n≤5,wi≤5

另外30%的数据满足:n≤1000,给定的树是一条链

100%的数据满足:T≤10,n≤20000,wi≤10000

解题思路:

来源:jzoj2490

刚拿到这道题的时候,一脸懵逼,反正最后看了半天没看出来,就写了一个最小边+1的贪心,不知道这次为什么这次数据那么毒瘤,一分都没拿到。

这道题其实和TYVJ1391走廊泼水节相似,只要在走廊泼水节的答案+原图的所有权值和就是答案了。可以从Kruskal的思想入手(可以理解为反着做Kruskal):把边按边权从小到大排序,判断当前边所连向的两点是否在同一联通块内,如果不是,则答案+=(两个联通块内的点互相连接的边数-1)*(当前边+1(+1是因为整张图只能有原图那一颗最小生成树,否则可能有多棵最小生成树)),所以我们可以再开一个数组存每个联通块的大小即可。

本题需要long long!

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 20200;
int T, n;
ll ans;
struct edge{
    int h, t;
    ll w;
}e[N];

inline int read() {
    int num = 0; bool flag = 1; char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-') flag = 0;
    for (; c >= '0' && c <= '9'; c = getchar())
        num = (num << 3) + (num << 1) + c - 48;
    return flag ? num : -num;
}

inline ll readll() {
    ll num = 0; bool flag = 1; char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-') flag = 0;
    for (; c >= '0' && c <= '9'; c = getchar())
        num = (num << 3) + (num << 1) + c - 48;
    return flag ? num : -num;
}

bool cmp(edge x, edge y) {
    return x.w < y.w;
}

int fa[N];
ll size[N];
inline int get(int x) {
    if (x == fa[x]) return x;
    return fa[x] = get(fa[x]);
}

int main() {
  freopen("input.in", "r", stdin);
  freopen("input.out", "w", stdout);
    T = read();
    while(T --) {
        ans = 0;
        n = read();
        for (int i = 1; i < n; ++ i) {
            e[i].h = read();
            e[i].t = read();
            e[i].w = readll();
            ans += e[i].w; 
        }
        sort(e + 1, e + n, cmp);
        for (int i = 1; i <= n; ++ i) fa[i] = i, size[i] = 1;
        for (int i = 1; i < n; ++ i) {
            int x = e[i].h, y = e[i].t; ll z = e[i].w;
            int fx = get(x), fy = get(y);
            if (fx != fy) {
                ans += (z + 1) * (size[fx] * size[fy] - 1LL);
                fa[fx] = fy;
                size[fy] += size[fx];
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

 

T2:三角形灯阵(triangle)

Describe:

中秋节的晚上,小x在桌面上放了许多好看的彩灯。遗憾的是,这些彩灯可能并非 全部都亮着。于是,小x打算把全部这些彩灯都点亮。

但是,小x很快发现,这些彩灯的摆放是非常有规律的,事实上,彩灯的位置都在平面的 正三角形镶嵌的某个交点处。距离为单位长度的彩灯被认为相互相邻。可以看出,每个彩灯 最多与六个彩灯相邻,相邻的彩灯都在以其为中心的单位正六边形的顶点上。

下图就是一种合法的彩灯摆放(对应样例数据):

其中,实心和空心的圆点分别代表亮与灭的彩灯,实线边代表彩灯间的相邻关系。

图中的边组成了若干单位正三角形(边长为单位长度的三角形),可以看出,每个彩灯 与它相邻的彩灯最多可以组成六个单位正三角形。而所有的单位三角形可被分成两类,我们 称为 A 类以及 B 类,图中加阴影的三角形属于 A 类。A 类三角形的特征是,三个顶点分别 在上方、左下方及右下方。

每个 A 类三角形中有一个开关(图中未画出),按动三角形中的开关 开关会改变三个顶点上每个彩灯的亮灭状态(亮变成灭,灭变成亮)。

那么,要点亮所有彩灯,最少需要按动多少次开关呢?

Hint:

对于 30%的数据,N<=100。

对于 100%的数据,3<=N<=50000。

解题思路:

拿到题目:题目怎么这么长,弄得我毫无思绪。一看数据范围(其实数据范围应该是一个突破点的)……还是去看T3吧233

仔细读题发现,当遇到一个A型三角形时,只要保证顶上那个点是亮的就行了吧,那么我们把这个点和跟这个点有关的边去掉,即这个点连向的点的入度--,如果这个点入度为0,那么入队,反复操作就好。

所以我们只要关注一个点左上、右上、左下、右下(其实我们让一个点与它左上、右上的点连一条有向边就好)的点,所以只要连接当前点与这些点即可(最开始应该把入度为0的点入队)

(说的不是很清楚,还是看die码吧,其实就是一个topsort的过程,所以才说数据范围是突破点)

Code:

#include<bits/stdc++.h>
using namespace std;

const int N = 50050, M = N * 6;
int n, m, ans = 0;
bool val[N];
int in[N];
int tot = 0, Link[M];
struct edge{
    int next, to;
}e[M];

inline void add(int x, int y) {
    e[++ tot].next = Link[x];
    Link[x] = tot;
    e[tot].to = y;
}

inline int read() {
    int num = 0; bool flag = 1; char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-') flag = 0;
    for (; c >= '0' && c <= '9'; c = getchar())
        num = (num << 3) + (num << 1) + c - 48;
    return flag ? num : -num;
}

queue <int> q;
void topsort() {
    for (int i = 1; i <= n; ++ i) 
        if (!in[i]) q.push(i);
    while(q.size()) {
        int x = q.front(); q.pop();
        if (!val[x]) ++ ans;
// 如果val[x]==0,即需要被点亮,则累计答案
for (int i = Link[x]; i; i = e[i].next) { int y = e[i].to; if (!val[x]) val[y] ^= 1;
// ^= 1 表示0变成1,1变成0
-- in[y]; if (!in[y]) q.push(y); } } } int main() { freopen("triangle.in", "r", stdin); freopen("triangle.out", "w", stdout); n = read(); char ch; for (int i = 1; i <= n; ++ i) val[i] = (bool)((int)getchar() - 48), ch = getchar(); m = read(); for (int i = 1; i <= m; ++ i) { int x = read(), y = read(), z = read(); if (z == 1 || z == 2) ++ in[y], add(x, y); if (z == 4 || z == 5) ++ in[x], add(y, x);
//注意连边方向 } topsort(); printf(
"%d\n", ans); return 0; }

 

T4:map(map)

Describe:

Hint:

 解题思路:

看到数据范围,发现n,q<=4000的直接暴力加边就好,树也是很显然,然而我写T3过于执着(悲催的是最后还没分),就没写这60分,有点伤……

我先想到一种方法,可以把图上每个强连通分量缩成一个点,然后整张图变成了一棵树,然后每个询问输出它们的距离(LCA即可)。然而我的这个方法很快就被yjz dalao叉掉了:这张图里可能有重边,那么就不会有树。

正解其实已经很接近了(估计是在想这个算法的时候题意理解错了吧),我们可以按照边双缩成一个点,然后

转载于:https://www.cnblogs.com/ckn1023/p/10420195.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值