CodeForces 472 D ,E,F (MST,构造,线性代数)(待补)

官方题解:http://codeforces.com/blog/entry/14028
E题:http://codeforces.com/contest/472/problem/E
F题:http://codeforces.com/contest/472/problem/F

There is an easy way to obtain a new task from an old one called “Inverse the problem”: we give an output of the original task, and ask to generate an input, such that solution to the original problem will produce the output we provided. The hard task of Topcoder Open 2014 Round 2C, InverseRMQ, is a good example.

Now let’s create a task this way. We will use the task: you are given a tree, please calculate the distance between any pair of its nodes. Yes, it is very easy, but the inverse version is a bit harder: you are given an n × n distance matrix. Determine if it is the distance matrix of a weighted tree (all weights must be positive integers).

Input
The first line contains an integer n (1 ≤ n ≤ 2000) — the number of nodes in that graph.

Then next n lines each contains n integers di, j (0 ≤ di, j ≤ 109) — the distance between node i and node j.

Output
If there exists such a tree, output “YES”, otherwise output “NO”.

Example
Input
3
0 2 7
2 0 9
7 9 0
Output
YES
Input
3
1 2 7
2 0 9
7 9 0
Output
NO
Input
3
0 2 2
7 0 9
7 9 0
Output
NO
Input
3
0 1 1
1 0 1
1 1 0
Output
NO
Input
2
0 0
0 0
Output
NO
Note
In the first example, the required tree exists. It has one edge between nodes 1 and 2 with weight 2, another edge between nodes 1 and 3 with weight 7.

In the second example, it is impossible because d1, 1 should be 0, but it is 1.

In the third example, it is impossible because d1, 2 should equal d2, 1.

大致题意:
告诉你一棵树有n个节点,然后以矩阵的形式告诉你每两个节点间的距离,问是否能构成一最小生成树。

思路:首先,我们可以排除掉一些错误答案,比如arr[i][i]!=0,arr[i][j]!=arr[j][i],arr[i][j]=0(i!=j)这几种我们可以直接输出NO。否则我们每次选择最短边然后构建一颗最小生成树,然后计算一遍两两节点之间的距离,如果和输入的矩阵不同怎输出NO。
节点间的距离我们可以先用LCA(最近公共祖先)的算法计算出i,j的最近公共祖先
那么dist[i][j] = dist[root][i] + dist[root][j] - 2 * dist[root][LCA[i][j]]

#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define LL long long  
#define ULL unsigned long long  
using namespace std;
LL gcd(LL a,LL b)
{
    if(b==0)return a;
    else return gcd(b,a%b);
}
LL lcm(LL a,LL b)
{
    return a/gcd(a,b)*b;
}
const int N=2005;
struct Edge{
    int  from ,to,v;
    Edge(int a,int b,int c):from(a),to(b),v(c) {}
    bool operator <(const Edge &e) const {
        return v<e.v;
    }
};
int n;
vector<Edge> edges;
vector<Edge> tree[N];
int arr[N][N];
int pre[N];
int father[N];
int vis[N];
int lca[N][N];
int dp[N];
int find(int x)
{
   int r=x;
   while (pre[r]!=r)
   r=pre[r];
   int i=x; int j;
   while(i!=r)
   {
       j=pre[i];
       pre[i]=r;
       i=j;
   }
   return r;
}
void join(Edge e)
{
    if(find(e.from)!=find(e.to))
    {
        pre[find(e.from)]=find(e.to);
        tree[e.from].push_back(Edge(e.from,e.to,e.v));
        tree[e.to].push_back(Edge(e.to,e.from,e.v));
    }
}
int getfather(int x)
{
    int r=x;
   while (father[r]!=r)
   r=father[r];
   int i=x; int j;
   while(i!=r)
   {
       j=father[i];
       father[i]=r;
       i=j;
   }
   return r;
}
void LCA(int x){
    vis[x]=1;
    for(int i=0;i<tree[x].size();i++){
        Edge e=tree[x][i];
        if(!vis[e.to])
        {
            LCA(e.to);
            father[e.to]=x;
         } 
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]&&!lca[i][x])
        {
            lca[i][x]=lca[x][i]=getfather(i);
        }
    }
}
void dfs(int x,int pre)
{
    for(int i=0;i<tree[x].size();i++)
    {
        Edge e=tree[x][i];
        if(e.to==pre) continue;
        dp[e.to]=dp[x]+e.v;
        dfs(e.to,x);
     } 
}
int main()
{
    int flag=1;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        pre[i]=father[i]=i;
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&arr[i][j]);
            if ((i == j && arr[i][j]) || (i != j && !arr[i][j]) || (i > j && arr[i][j] != arr[j][i])) {  
                flag=0;
            }
            if(i<j)
            edges.push_back(Edge(i,j,arr[i][j]));
        }
    }
    if(flag==0)
    {
        printf("NO\n");
        return 0;
    }
    sort(edges.begin(),edges.end());
    for(int i=0;i<edges.size();i++)
    {
        join(edges[i]);
    }
    LCA(1);
    dfs(1,0);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++){
            int dist=dp[i]+dp[j]-dp[lca[i][j]]*2;
            if(dist!=arr[i][j]){
                printf("NO\n");
                return 0;

            }       
        }
    }
    printf("YES\n");
    return 0; 
}

472D - Design Tutorial: Inverse the Problem

Let’s think it in the following way: for the minimal length edge, it must belong the the tree, …, for the k-th minimal length edge(a, b), if there is already an path between a-b, then it can not be an edge of tree anymore, otherwise it must be edge of tree, why? Because otherwise there must be a path from a to b in the tree, that means a and b can be connected by edges with less length, but a and b is not connected.

So this Kruskal style analysis gives us this theorem: if there is an answer, then the answer must be the MST of that graph. (You can also guess this theorem and try to prove it.)

You can solve MST in O(n^2 log n), and then there are many way to check distance between notes on tree: you can just simply do dfs or bfs from each node, it can run in O(n^2). Or if you have pre-coded LCA algorithm, it can run in O(n^2 log n).

472E - Design Tutorial: Learn from a Game

First let’s solve some special cases:

If the initial board and the target board contains different orbs, then there can’t be a solution.

If n = 1 (or m = 1), then we can try all O(m^2) (or O(n^2)) possible moves.

And for the other cases, there always have solution. We first hold the orb with number target[1][1] in initial board. Then we want to move other orbs to their position.

So let’s come up with a process to move orb from (a, b) to (c, d): it must be some continue path from (a, b) to (c, d), so we want to build a single move: it will move an orb from (a, b) to an adjacent cell (c, d). How to do that? We can move our touching orb to (c, d) first, and then move to (a, b). But there are some concerns: in this move, the touching orb shouldn’t pass any already-done cells, and it shouldn’t pass (a, b) when we get to (c, d).

That means we need a good order to move orbs. We can do it in this way: first, as long as there are more than 2 rows, we move the orbs to last row (from left to right or right to left). And then it must be 2xm boards: we do it column by column from right to left. We can find that in this order, there always exist paths for our touching orb to get (c, d).

472F - Design Tutorial: Change the Goal

You need to know some knowledge about linear algebra and notice the operation of xor on 32 bit integers equals to + operation in a 32 dimension linear space. If you don’t know, you should learn it from the editorial of similar tasks, for example, Topcoder SRM 557 Div1-Hard.

We need to know some basic properties of our operation:

we can swap two number a and b by: a^=b, b^=a, a^=b.

This operation is inversible, the inverse is itself.

By property 1 we can do the Gaussian elimination of each set of vectors.

By property 2 we can use this way to build an answer: use some operation to make A[] into a base (linear independent vectors that the span will be A[]), then transfer it into a base of B[], then use the inverse of Gaussian elimination to recover B[].

So now we have two bases: {a1, a2, …, ax} and {b1, b2, …, by}. If there exists some bi such that it can’t be expressed as a linear combination of {a1, a2, …, ax}, the solution can’t exists.

Otherwise there always exists a solution: first we build b1 and put it in the first position. Suppose b1 = a2 ^ a3 ^ a8, then we swap any of them, say, a2 into position one, and then xor it with a3 and a8, then we get b1. Note that after this operation {a1, a2, …, ax} will remain a base. And we need to ensure we don’t erase already-done numbers in a.

j

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值