DP&图论 DAY 6 下午 考试

DP&图论  DAY 6  下午  考试

3
5 10 3
1 3 437
1 2 282
1 5 328
1 2 519
1 2 990
2 3 837
2 4 267
2 3 502
3 5 613
4 5 132
1 3 4
10 13 4
1 6 484
1 3 342
2 3 695
2 3 791
2 8 974
3 9 526
4 9 584
4 7 550
5 9 914
6 7 444
6 8 779
6 10 350
8 8 394
9 10 3 7
10 9 4
1 2 330
1 3 374
1 6 194
2 4 395
2 5 970
2 10 117
3 8 209
4 9 253
5 7 864
8 5 10 6

样例输入

437
526
641

样例输出

题解

>50 pt      dij 跑暴力

(Floyd太慢了QWQ    O(n^3))

枚举每个点作为起点,dijkstra,跑暴力  O( (n+m)logn ),寻找全局最短路

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>

using namespace std;

inline int read()
{
    int ans=0;
    char last=‘ ‘,ch=getchar();
    while(ch<‘0‘||ch>‘9‘) last=ch,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘) ans=ans*10+ch-‘0‘,ch=getchar();
    if(last==‘-‘) ans=-ans;
    return ans;
}

typedef pair<int,int> pa;
const int maxn=1e5+10;
const int maxm=2e5+10;
int T;
int n,m,k;
struct node
{
    int to,nxt,dis;
}edge[maxm];
int head[maxn],cnt=0;
void addedge(int u,int v,int w)
{
    edge[++cnt].to =v;edge[cnt].dis =w;edge[cnt].nxt =head[u];head[u]=cnt;
    edge[++cnt].to =u;edge[cnt].dis =w;edge[cnt].nxt =head[v];head[v]=cnt;
}
int dis[maxn];
int a[maxn];

void dijkstra(int s)
{
    priority_queue<pa,vector<pa>,greater<pa> >q;
    q.push(make_pair(s,dis[s]=0));
    while(!q.empty() )
    {
        pa now=q.top();
        q.pop() ;
        if(now.second !=dis[now.first ]) continue;
        for(int i=head[now.first ],v;i;i=edge[i].nxt )
        {
            if(v=edge[i].to ,dis[v]>dis[now.first ]+edge[i].dis )
              q.push(make_pair(v,dis[v]=dis[now.first]+edge[i].dis));
        }
    }
}

int main()
{
    T=read();
    for(int t=1;t<=T;t++)
    {
        cnt=0;
        memset(head,0,sizeof(head));
        memset(edge,0,sizeof(edge));
        memset(a,0,sizeof(a));
        n=read();m=read();k=read();
        for(int i=1;i<=m;i++)
        {
            int u=read(),v=read(),w=read();
            addedge(u,v,w);
        }

        for(int i=1;i<=k;i++) a[i]=read();
        int ans=0x3f3f3f3f;
        for(int i=1;i<=k;i++)
        {
            memset(dis,0x3f,sizeof(dis));
            dijkstra(a[i]);
            for(int j=1;j<=k;j++)
            {
                   if(a[i]==a[j]) continue;
                   ans=min(ans,dis[a[j]]);
            }
        }
        if(ans>=0x3f3f3f3f) ans=-1;
        printf("%d\n",ans);

    }
    return 0;
}

>100pt    考虑优化枚举量

因为答案是两个编号不同的点,所以对应的二进制编码至少有一位不同

枚举二进制的每一位

假设枚举到第 i 位,把这一位是 1 的点设置为源点,是 0  的设置为汇点,跑一遍多源多汇最短路

设置一个超级源点,向所有第一层集合的点连一条长度为 0 的边

设置一个超级汇点,所有最后一个集合的点向超级汇点连一条长度为 0  的边

跑从超级源点到超级汇点的最短路

跑最多32次就可以得到答案

这两个集合既可以是 1~n ,也可以是 1~k

显然 1~k 更优

#include <queue>
#include <cstdio>
#include <cstring>

template <class cls>
inline cls min(const cls & a, const cls & b) {
    return a < b ? a : b;
}

const int mxn = 100005;
const int mxm = 500005;
const int inf = 0x3f3f3f3f;

int n, m, k;

int points[mxn];

int tot;
int hd[mxn];
int nt[mxm];
int to[mxm];
int vl[mxm];

inline void add_edge(int u, int v, int w) {
    nt[++tot] = hd[u];
    to[tot] = v;
    vl[tot] = w;
    hd[u] = tot;
}

int dis[mxn];

struct data {
    int u, d;

    data(int _u, int _d) :
        u(_u), d(_d) {}

    bool operator < (const data & that) const {
        return d > that.d;
    }
};

std::priority_queue<data> heap;

int main() {
    int cas;
    scanf("%d", &cas);
    for (int c = 0; c < cas; ++c) {
        scanf("%d%d%d", &n, &m, &k);
        memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;
        for (int i = 0, u, v, w; i < m; ++i) {
            scanf("%d%d%d", &u, &v, &w);
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
        for (int i = 0; i < k; ++i)
            scanf("%d", points + i);
        int ans = inf;
        for (int i = 1; i < k; i <<= 1) {
            memset(dis, inf, sizeof(int) * (n + 5));
            for (int j = 0, p; j < k; ++j)
                if (p = points[j], (j & i) == 0)
                    heap.push(data(p, dis[p] = 0));
            while (!heap.empty()) {
                int u = heap.top().u;
                int d = heap.top().d;
                heap.pop();
                if (dis[u] != d)
                    continue;
                for (int e = hd[u], v, w; e; e = nt[e])
                    if (v = to[e], w = vl[e], dis[v] > d + w)
                        heap.push(data(v, dis[v] = d + w));
            }
            for (int j = 0, p; j < k; ++j)
                if (p = points[j], (j & i) != 0)
                    ans = min(ans, dis[p]);
        }
        printf("%d\n", ans == inf ? -1 : ans);
    }
    return 0;
}


3
5 10 5
4 10 8 1 10
1 3
1 4
1 5
1 3
2 1
2 5
4 3
4 3
4 5
5 1
1 4
4 6
1 9
4 7
2 9
5 10 5
2 8 8 10 10
2 1
2 3
3 2
3 4
3 1
3 2
3 4
4 1
5 4
5 1
1 4
2 3
4 7
3 10
1 5
5 10 5
9 9 8 2 1
1 5
1 5
2 1
2 4
2 4
2 4
3 2
3 1
4 3
4 3
5 9
3 9
2 7
5 1
5 4

样例输入

40
60
90
70
90
8
30
70
100
10
9
81
63
14

样例输出

 题解

50pt    dfs  暴力

观察题目发现我们只需要找到对于一个点的  就好

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>

using namespace std;

typedef long long ll;

inline ll read()
{
    ll ans=0;
    char last=‘ ‘,ch=getchar();
    while(ch<‘0‘||ch>‘9‘) last=ch,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘) ans=ans*10+ch-‘0‘,ch=getchar();
    if(last==‘-‘) ans=-ans;
    return ans;
}

const ll maxn=2e5+10;
const ll maxm=4e5+10;
ll T;
ll n,m,k;
ll w[maxn],son[maxn];
ll head[maxn],to[maxm],nxt[maxn];
bool vis[maxn];

void addedge(ll u,ll v,ll cnt)
{
    to[cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}

void chuli(ll u)
{
    if(head[u]==0) return;
    if(vis[u]) return;
    vis[u]=1;
    for(ll i=head[u];i;i=nxt[i])
    {
        ll v=to[i];
        chuli(v);
        if(w[son[v]]>w[son[u]]) son[u]=son[v];
    }
    return;
}

int main()
{
//    freopen("neural.in","r",stdin);
//    freopen("ceshi.txt","w",stdout);
    T=read();
    for(ll t=1;t<=T;t++)
    {
        memset(w,0,sizeof(w));
        memset(son,0,sizeof(son));
        memset(head,0,sizeof(head));
        memset(nxt,0,sizeof(nxt));
        memset(to,0,sizeof(to));
        memset(vis,0,sizeof(vis));

        n=read();m=read();k=read();
        for(ll i=1;i<=n;i++)
           w[i]=read(),son[i]=i;
        for(ll i=1;i<=m;i++)
        {
            ll u,v;
            u=read();v=read();
            addedge(u,v,i);
        }
        for(ll i=1;i<=n;i++) chuli(i);
        for(ll i=1;i<=k;i++)
        {
            ll u,x;
            u=read();x=read();
            printf("%lld\n",(long long)x*w[son[u]]);
        }
    }
    return 0;
}

>100pt

建反向边,tarjan然后拓扑就行了

(   然后我们发现一个大佬ych的思路

思路是tarjan缩点,一个强连通分量的初始ans就是这个强连通分量里面点的最大值。然后建立新图,找到入度为0的点开始dfs,然后更新强连通分量的ans。

询问点就是找点所在的强连通分量,输出强连通分量的ans就ok  )

#include <cstdio>
#include <cstring>

template <class cls>
inline cls min(const cls & a, const cls & b) {
    return a < b ? a : b;
}

template <class cls>
inline cls max(const cls & a, const cls & b) {
    return a > b ? a : b;
}

const int mxn = 200005;
const int mxm = 400005;

int n, m, k, w[mxn];

struct edge {
    int u, v;
} edges[mxm];

int tot;
int hd[mxn];
int to[mxm << 1];
int nt[mxm << 1];

inline void add_edge(int u, int v) {
    nt[++tot] = hd[u];
    to[tot] = v;
    hd[u] = tot;
}

int tim;
int cnt;
int top;
int dfn[mxn];
int low[mxn];
int stk[mxn];
int scc[mxn];

void tarjan(int u) {
    dfn[u] = low[u] = ++tim; stk[++top] = u;
    for (int e = hd[u], v; e; e = nt[e])
        if (v = to[e], scc[v] == 0) {
            if (dfn[v] == 0)tarjan(v),
                low[u] = min(low[u], low[v]);
            else
                low[u] = min(low[u], dfn[v]);
        }
    if (dfn[u] == low[u]) {
        cnt += 1;
        do {
            scc[stk[top]] = cnt;
        } while (stk[top--] != u);
    }
}

int oe[mxn];
int mx[mxn];

int que[mxn];

void bfs() {
    int l = 0, r = 0;
    for (int i = 1; i <= cnt; ++i)
        if (oe[i] == 0)
            que[r++] = i;
    while (l < r) {
        int u = que[l++];
        for (int e = hd[u], v; e; e = nt[e])
            if (v = to[e], mx[v] = max(mx[v], mx[u]), --oe[v] == 0)
                que[r++] = v;
    }
}

int main() {
    int cas;
    scanf("%d", &cas);
    for (int c = 0; c < cas; ++c) {
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n; ++i)
            scanf("%d", w + i);
        memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;
        for (int i = 0; i < m; ++i) {
            scanf("%d%d", &edges[i].u, &edges[i].v);
            add_edge(edges[i].u, edges[i].v);
        }
        tim = cnt = top = 0;
        memset(scc, 0, sizeof(int) * (n + 5));
        memset(dfn, 0, sizeof(int) * (n + 5));
        for (int i = 1; i <= n; ++i)
            if (scc[i] == 0)
                tarjan(i);
        memset(hd, 0, sizeof(int) * (cnt + 5)); tot = 0;
        memset(oe, 0, sizeof(int) * (cnt + 5));
        memset(mx, 0, sizeof(int) * (cnt + 5));
        for (int i = 0; i < m; ++i) {
            int u = scc[edges[i].u];
            int v = scc[edges[i].v];
            if (u != v)
                add_edge(v, u), oe[u] += 1;
        }
        for (int i = 1; i <= n; ++i)
            mx[scc[i]] = max(mx[scc[i]], w[i]);
        bfs();
        for (int i = 0, u, x; i < k; ++i) {
            scanf("%d%d", &u, &x);
            printf("%lld\n", 1LL * x * mx[scc[u]]);
        }
    }
    return 0;
}


样例输入

2
3
1
0
2
0
2
2
1
0

样例输出

题解

很像 Qtree 啊 所以一样hintai

树链剖分

单点修改,查询区间内值为x的数

考虑如何实现???

如果x比较少,完全可以建几棵线段树来实现,就好比 20% 的数据,颜色种类不超过 5

每次修改一个颜色,就是在该颜色线段树内 +1,原颜色线段树内 -1

颜色种类多了怎么办?

暴力:开100个树状数组,和刚才没什么区别

如果线段树在每一个节点上维护一个100的数组

合并的时候可以直接暴力统计节点次数,这样代价是区间长度

如果每一位枚举则是n*100

每一层访问的点是n的,一共log层

复杂度 O(nlogn)

继续优化:

离线操作,只需要建一棵线段树

操作分类,与同一种颜色有关的操作放到一起

所有操作次数相加就是2m

所以操作还是o(m)

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

inline int getint()
{
    int r = 0, c = getchar();

    for (; c < 48; c = getchar());
    for (; c > 47; c = getchar())
        r = r * 10 + c - 48;

    return r;
}

const int mxc = 100005;
const int mxn = 100005;
const int mxm = 200005;

int n, m, c;

int tt;
int hd[mxn];
int to[mxm];
int nt[mxm];

inline void addedge(int x, int y)
{
    nt[++tt] = hd[x], to[tt] = y, hd[x] = tt;
    nt[++tt] = hd[y], to[tt] = x, hd[y] = tt;
}

struct data
{
    int k, x, y;

    data() {} ;
    data(int a, int b, int c)
        : k(a), x(b), y(c) {} ;
};

int color[mxn];

#include <vector>

vector<data> vec[mxc];

int tim;
int dfn[mxn];
int top[mxn];
int fat[mxn];
int dep[mxn];
int son[mxn];
int siz[mxn];

void dfs1(int u, int f)
{
    siz[u] = 1;
    son[u] = 0;
    fat[u] = f;
    dep[u] = dep[f] + 1;

    for (int i = hd[u], v; i; i = nt[i])
        if (v = to[i], v != f)
        {
            dfs1(v, u);
            siz[u] += siz[v];
            if (siz[v] > siz[son[u]])
                son[u] = v;
        }
}

void dfs2(int u, int f)
{
    dfn[u] = ++tim;

    if (son[f] == u)
        top[u] = top[f];
    else
        top[u] = u;

    if (son[u])
        dfs2(son[u], u);

    for (int i = hd[u], v; i; i = nt[i])
        if (v = to[i], v != f && v != son[u])
            dfs2(v, u);
}

int bit[mxn];

inline void add(int p, int v)
{
    for (; p <= n; p += p & -p)
        bit[p] += v;
}

inline int ask(int l, int r)
{
    int sum = 0; --l;

    for (; r; r -= r & -r)
        sum += bit[r];

    for (; l; l -= l & -l)
        sum -= bit[l];

    return sum;
}

int ans[mxn];

signed main()
{
    int cas = getint();

    while (cas--)
    {
        n = getint();
        m = getint();

        for (int i = 1; i <= n; ++i)
            vec[color[i] = getint()].push_back(data(0, i, +1));

        c = 0;

        for (int i = 1; i <= n; ++i)
            c = max(c, color[i]);

        memset(hd, 0, sizeof(int) * (n + 5)); tt = 0;

        for (int i = 1; i < n; ++i)
        {
            int x = getint();
            int y = getint();

            addedge(x, y);
        }

        for (int i = 1; i <= m; ++i)
        {
            if (getint() == 1)
            {
                int p = getint();
                int a = color[p];
                int b = color[p] = getint();

                vec[a].push_back(data(0, p, -1));
                vec[b].push_back(data(0, p, +1));
            }
            else
            {
                int x = getint();
                int y = getint();
                int k = getint();

                vec[k].push_back(data(i, x, y));
            }
        }

        dfs1(1, 0);
        dfs2(1, 0);

        memset(ans, -1, sizeof ans);

        for (int k = 1; k <= c; ++k)
        {
            int sz = vec[k].size();

            memset(bit, 0, sizeof bit);

            for (int i = 0; i < sz; ++i)
            {
                const data &d = vec[k][i];

                ans[d.k] = 0;

                if (d.k == 0)
                    add(dfn[d.x], d.y);
                else
                {
                    int a = d.x, ta = top[a];
                    int b = d.y, tb = top[b];

                    while (ta != tb)
                    {
                        if (dep[ta] >= dep[tb])
                            ans[d.k] += ask(dfn[ta], dfn[a]), ta = top[a = fat[ta]];
                        else
                            ans[d.k] += ask(dfn[tb], dfn[b]), tb = top[b = fat[tb]];
                    }

                    if (dep[a] <= dep[b])
                        ans[d.k] += ask(dfn[a], dfn[b]);
                    else
                        ans[d.k] += ask(dfn[b], dfn[a]);
                }
            }
        }

        for (int i = 1; i <= m; ++i)
            if (ans[i] >= 0)
                printf("%d\n", ans[i]);

        for (int i = 1; i <= c; ++i)
            vec[i].clear();

        tim = 0;
    }

    return 0;
}

原文地址:https://www.cnblogs.com/xiaoyezi-wink/p/11336877.html

时间: 2024-11-02 21:08:22

DP&图论 DAY 6 下午 考试的相关文章

DP&amp;图论 DAY 2 下午 考试

Problem AProblem Description有一天 Tarzan 写了一篇文章,我们发现这文章当中一共出现了 n 个汉字,其中第 i 个汉字出现了 ai 次,因为 Tarzan 不希望文章被别人偷看,他要给这 n 个字分别用一个特殊的字符串表示,其中每一个字符串由两类字符组构成,一类是 a,另一类是 ha,例如 ahaha 就是由 a. ha 和 ha 构成的,我们希望帮助 Tarzan 给每个汉字一个独一无二的字符串,其中不能存在两个字符串一个是另一个的前缀.我们同时希望文章尽量短

DP&amp;图论 DAY 4 下午图论

DP&图论  DAY 4  下午 后天考试不考二分图,双联通 考拓扑排序 图论 图的基本模型 边: 有向边构成有向图 无向边构成无向图 权值: 1.无权 2.点权 3.边权 4.负权(dij不可以跑) 环: 1. 2.重边 3.有向无环图DAG 路径: 1.简单路径:不经过重复的点  1-->2-->3 不简单路径:经过重复点  1-->2-->3-->1-->4 2.连通,具有传递性 图: 1.树:n个点,n-1条边的无环连通图 2.完全图:一个无向图,图中任

DP&amp;图论 DAY 5 下午

DP&图论  DAY 5  下午 树链剖分  每一条边要么属于重链要么轻边 证明: https://www.cnblogs.com/sagitta/p/5660749.html 轻边重链都是交替走的(此处重链可以走若干条边) 1.dfs1 统计子树大小,确定重儿子 2.dfs2 找重链       重链,子树,分别是连续的一段 每个结点属于一个重链 ta < tb a 跳到 ta 的父节点 logn 级别 将树序列化 SPOJ QTREE Query on a tree Solution 树

DP&amp;图论 DAY 3 上午

DP&图论  DAY 3  上午 状态压缩dp >状态压缩dp ?状态压缩是设计dp状态的一种方式.?当普通的dp状态维数很多(或者说维数与输入数据有关),但每一维总量很少是,可以将多维状态压缩为一维来记录.?这种题目最明显的特征就是: 都存在某一给定信息的范围非常小(在20以内),而我们在dp中所谓压缩的就是这一信息.?(或者是在做题过程中分析出了某一信息种类数很少)?我们来看个例子. >经典题?给出一个n*m的棋盘,要放上一些棋子,要求不能有任意两个棋子相邻.求方案数.? n<

【2019.8.14】2019QB学堂DP图论班第一次考试 Problem C

Problem C Problem Description Tarzan 非常烦数轴因为数轴上的题总是难度非常大.不过他非常喜欢线段,因为有关线 段的题总是不难,讽刺的是在一个数轴上有 n 个线段,Tarzan 希望自己喜欢的东西和讨厌的 东西不在一起,所以他要把这些线段分多次带走,每一次带走一组,最多能带走 k 次.其实 就是要把这些线段分成至多 k 组,每次带走一组,问题远没有那么简单,tarzan 还希望每次 选择的线段组都很有相似性,我们定义一组线段的相似性是组内线段交集的长度,我们现在

换教室 vijos2005 NOIP2016 D1T3 期望DP 图论最短路(雾)

直接上代码吧 Debug一下午 心累..今天让我对memset有了完整的认识 #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; template<class T> inline void read(T &_a){ bool f=0;int _ch=getchar();_a=0; while(_ch<'

QBXT2018 4 29 DP&amp;图论班 胡策 by zhx

PS:由于长者是以我们没有学过图论与DP的基础上出的题,所以可能(?)会有点水. 题意: T1:判断括号序列是是否合法.可以利用栈,别忘了判断最终栈是否为空. #include<cstdio> #include<cstdlib> #include<cstring> using namespace std; char s[1000],z[1000]; int size; int main() { scanf("%s",s+1); int l=strle

两道很好的dp题目【4.29考试】

A 问题描述: 对于一个排列,考虑相邻的两个元素,如果后面一个比前面一个大,表示这个位置是上升的,用I表示,反之这个位置是下降的,用D表示.如排列3,1,2,7,4,6,5可以表示为DIIDID. 现在给出一个长度为n-1的排列表示,问有多少种1到n的排列满足这种表示. 输入: 一个字符串S,S由I,D,?组成.?表示这个位置既可以为I,又可以为D. 输出: 有多少种排列满足上述字符串.输出排列数模1000000007 样例输入: ?D 样例输出: 3 数据范围: 20%的数据 S长度<=10

QBXT 2018春季DP&amp;图论班 2018.5.3 --- 区间DP专题

本文题目等来自北京大学张浩威学长的PPT. 1.区间DP:解决有关两个或以上的区间的合并或删除的问题(最大/小次数/价值.方案总数.可行性等). 2.石子合并: 有n堆石子排成一排,第i堆石子的个数为ai.每次可以将相邻两堆合并成一堆.合并的代价为两堆石子的石子个数之和.设计方案要求代价之和最小. 状态:dp[l][r]表示只考虑区间l~r的石子,将它们合并的最小代价. 状态转移:dp[l][r]=min{dp[l][r],dp[l][k]+dp[k+1][r]+s[r]-s[l-1]} l~r