日记2016.10.12 + 2016.10.13

周记变成日记的格式了。。。
先来总结下今天白天做的题目。。。
1036: [ZJOI2008]树的统计Count
10分钟敲完就A的树剖,没什么可说的。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define v edge[i].to
#define RepG(i,x) for(int i = head[x];~ i;i = edge[i].next)
using namespace std;
const int N = 100005;
typedef long long LL;
struct Edge{int next,to;}edge[N];
int val[N],mx[N],fa[N],sz[N],dep[N],top[N],cnt,head[N],son[N],tim,tid[N],Rank[N],qr,ql,n,m;
LL sum[N];
void save(int a,int b){edge[cnt] = (Edge){head[a],b},head[a] = cnt ++;}
void dfs(int x)
{
    sz[x] = 1;
    RepG(i,x)
    {
        if(v != fa[x])
        {
            fa[v] = x;
            dep[v] = dep[x] + 1;
            dfs(v);
            sz[x] += sz[v];
            if(sz[son[x]] < sz[v])
                son[x] = v;
        }
    }
}
void dfs(int x,int tp)
{
    tid[x] = ++ tim;
    Rank[tim] = x;
    top[x] = tp;
    if(!son[x])return ;
    dfs(son[x],tp);
    RepG(i,x)
    {
        if(v != son[x] && v != fa[x])
        {
            dfs(v,v);
        }
    }
}
#define mid (l + r >> 1)
#define lson x << 1 ,l,mid 
#define rson x << 1 | 1,mid + 1,r
void Upd(int x)
{
    mx[x] = max(mx[x << 1],mx[x << 1 | 1]);
    sum[x] = sum[x << 1] + sum[x << 1 | 1];
}
void Build(int x,int l,int r)
{
    if(l == r)
    {
        mx[x] = val[Rank[l]];
        sum[x] = mx[x];
        return;
    }
    Build(lson),Build(rson);
    Upd(x);
}
void Modify(int x,int l,int r)
{
    if(l == r)
    {
        mx[x] = qr;
        sum[x] = qr;
        return ;
    }
    ql <= mid ? Modify(lson) : Modify(rson);
    Upd(x);
}
LL QryS(int x,int l,int r)
{
    if(ql <= l && r <= qr)return sum[x];
    LL cur = 0;
    if(ql <= mid)cur += QryS(lson);
    if(mid < qr)cur += QryS(rson);
    return cur;
}
int Qry(int x,int l,int r)
{
    if(ql <= l && r <= qr)return mx[x];
    int cur = - 1 << 30;
    if(ql <= mid)cur = max(Qry(lson),cur);
    if(mid < qr)cur = max(Qry(rson),cur);
    return cur;
}
void Query(int x,int y,int tp)
{
    LL now = 0;int cur = - 1 << 30;
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]])swap(x,y);
        ql = tid[top[x]],qr = tid[x];
        if(tp & 1)now += QryS(1,1,n);
        else cur = max(cur,Qry(1,1,n));
        x = fa[top[x]];
    }
    ql = tid[x],qr = tid[y];
    if(ql > qr)swap(ql,qr);
    if(tp & 1)now += QryS(1,1,n);
    else cur = max(cur,Qry(1,1,n));
    if(tp & 1)printf("%lld\n",now);
    else printf("%d\n",cur);
}
int main()
{
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    int a,b;
    Rep(i,n - 1)scanf("%d%d",&a,&b),save(a,b),save(b,a);
    Rep(i,n)scanf("%d",&val[i]);
    dfs(1),dfs(1,1);Build(1,1,n);
    scanf("%d",&m);
    char op[10];
    while(m -- )
    {
        scanf("%s",op);
        int x,y;
        scanf("%d%d",&x,&y);
        if(op[0] == 'C')ql = tid[x],qr = y,Modify(1,1,n);
        else if(op[2] == 'A')Query(x,y,0);
        else Query(x,y,1);
    }
    return 0;
}

1034: [ZJOI2008]泡泡堂BNB
田忌赛马嘛。
考虑到,如果最差的不能战胜最差的,且最强的不能战胜最强的的话,那么我们肯定把自己这边的最差的人和最强选手PK。
这样的话,我们只需要sort两遍即可。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int N = 400001;
int a[N],b[N],n;
int solve(int* a,int* b)
{
    int l1 = 1,l2 = 1,r1 = n,r2 = n;
    int cur = 0;
    while(l1 <= r1 && l2 <= r2)
    {
        if(a[l1] > b[l2])cur += 2,l1 ++,l2 ++;
        else if(a[r1] > b[r2])cur += 2,r2 --,r1 --;
        else if(a[l1] == b[r2])cur ++,l1 ++,r2 --;
        else l1 ++,r2 --;
    }
    return cur;
}
int main()
{
    scanf("%d",&n);
    Rep(i,n)scanf("%d",&a[i]);
    sort(a + 1,a + 1 + n);
    Rep(i,n)scanf("%d",&b[i]);
    sort(b + 1,b + 1 + n);
    printf("%d %d\n",solve(a,b),2 * n - solve(b,a));
    return 0;
}

bzoj1029: [JSOI2007]建筑抢修
排序+堆来贪心是很显然的,但是怎么贪呢……
这个感觉自己需要学习一个。
首先,我们考虑到用堆维护反悔序列
按照截止时间排序。
那么我们就先不管优不优秀,直接把东西丢进去。
然后忽然发现,如果再加入一个东西后,会超出截止时间,那么我们就考虑到:可不可以把之前满足条件的比较差的解移除,然后加入这个解。
显然是可以的。
如果堆顶的修复时间>这个物品的修复时间,并且我们也能在除掉堆顶的之后就能放下这个物品,那么这个物品比堆顶更优秀是显然的,并且也必然是合法的。
否则的话这个物品显然不做考虑。


#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
struct Node{int tim,T;}a[150002];
priority_queue<Node>q;
bool operator <(Node x,Node y)
{
    return y.tim > x.tim;
}
bool cmp(Node x,Node y){return x.T < y.T;}
int n;
int main()
{
    scanf("%d",&n);
    Rep(i,n)scanf("%d%d",&a[i].tim,&a[i].T);
    sort(a + 1,a + 1 + n,cmp);
    int T = 0,cur = 0;
    Rep(i,n)
    {
        if(T + a[i].tim <= a[i].T)T += a[i].tim,q.push(a[i]),cur ++;
        else if(a[i].tim < q.top().tim && T + a[i].tim - q.top().tim <= a[i].T)
        {
            T = T + a[i].tim - q.top().tim;
            q.pop();
            q.push(a[i]);
        }
    }
    printf("%d\n",cur);
    return 0;
}


1012: [JSOI2008]最大数maxnumber
这个题吧。
我们直接开个20W的线段树然后就A掉了。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define mid (l + r >> 1)
#define lson x << 1,l,mid
#define rson x << 1 | 1,mid + 1,r
using namespace std;
int n,m,cur,Mod,L,mx[1000005],ql,qr;
void Ins(int x,int l,int r)
{
    if(l == r)
    {
        mx[x] = L;
        return ;
    }
    n <= mid ? Ins(lson) : Ins(rson);
    mx[x] = max(mx[x << 1],mx[x << 1 | 1]);
}
int Qry(int x,int l,int r)
{
    if(l >= ql && r <= qr)return mx[x];
    int cur = 0;
    if(ql <= mid)cur = max(cur,Qry(lson));
    if(mid < qr)cur = max(cur,Qry(rson));
    return cur;
}
int main()
{
    scanf("%d%d",&m,&Mod);
    char op[3];
    int ans = 0;
    while(m --)
    {
        scanf("%s",&op);
        if(op[0] == 'A')scanf("%d",&L),L = (L + ans) % Mod,++ n,Ins(1,1,200000);//,printf("Insert %d\n",n);
        else
        {
            scanf("%d",&L);
            ql = n - L + 1,qr = n;
            ans = Qry(1,1,200000);
            printf("%d\n",ans);
        }
    }
    return 0;
}


1009: [HNOI2008]GT考试
这个非常经典!
我觉得绝对是个矩阵乘法好题!
定义 f[i][j] 为第 i 个号码,从开头匹配了j位的方案数。
首先我们考虑到 f[i][j] f[i1][k] 转移好像不是那么简单。
我们仔细想想,肯定是:
f[i+1][j]+=f[i][k]+c
嗯……其实我想表达的意思是:
k 这个下标开始,加上字符c之后,和原串从开头匹配的字符长度为 j
那么显然的一点是,这个东西肯定是可以写成矩阵乘法形式的!
我们枚举一下字符之后就可以知道转移矩阵是什么了。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
typedef long long LL;
int n,m,K,nxt[25];
char s[25];
struct Matrix 
{
    int a[22][22];
    Matrix(){memset(a,0,sizeof(a));}
    void set(){memset(a,0,sizeof(a));for(int i = 0;i <= m;++ i)a[i][i] = 1;}
}it;
Matrix operator *(const Matrix &x,const Matrix &y)
{
    Matrix z = Matrix();
    for(int i = 0;i < m;++ i)
        for(int k = 0;k < m;++ k)
            for(int j = 0;j < m;++ j)
                (z.a[i][j] += x.a[i][k] * y.a[k][j]) %= K;
    return z;
}
void print(Matrix a)
{
    for(int i = 0;i < m;++ i){for(int j = 0;j < m;++ j)printf("%d ",a.a[i][j]);puts("");}
}
Matrix Power(Matrix a,int p)
{
    Matrix cur;
    cur.set();
    while(p)
    {
        if(p & 1)cur = cur * a;
        p >>= 1;
        a = a * a;
    }
    return cur;
}
void Init()
{
    int len = m,j = 0;
    nxt[1] = 0;
    for(int i = 2;i <= len;++ i)
    {
        while(j && s[i] != s[j + 1])j = nxt[j];
        if(s[i] == s[j + 1])++ j;
        nxt[i] = j;
    }
    for(int i = 0;i < len;++ i)
    {
        for(int j = '0';j <= '9';++ j)
        {
            int t = i;
            while(t && s[t + 1] != j)t = nxt[t];
            if(s[t + 1] == j)++ t;
            if(t != len)it.a[t][i] = (it.a[t][i] + 1) % K;
        }
    }
    it = Power(it,n);
}
int main()
{
    scanf("%d%d%d",&n,&m,&K);   
    scanf("%s",s + 1);
    Init();
    LL cur = 0;
    for(int i = 0;i < m;++ i)
        (cur = cur + it.a[i][0]) %= K;
    printf("%lld\n",cur);
    return 0;
}

1007: [HNOI2008]水平可见直线
我们可以发现,一堆直线往下看的时候,显然是这些直线组成的下凸壳。这样的话,按照斜率排序后,我们维护一个栈,如果栈顶和当前直线的交点在栈顶和栈顶第二个元素的交点的第二个,这样的话就可以完全覆盖这条直线。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int N = 100002;
const double eps = 1e-8;
int n,tp,seq[N];
struct Line{double a,b;int id;}l[N],stk[N];
bool cmp(Line x,Line y){return fabs(x.a - y.a) < eps ? x.b < y.b : x.a < y.a;}
double CX(Line x,Line y){return (x.b - y.b) / (y.a - x.a);}
void Ins(Line x)
{
    while(tp)
    {
        if(fabs(stk[tp].a - x.a) < eps)tp --;
        else if(tp > 1 && CX(x,stk[tp]) < eps + CX(stk[tp],stk[tp - 1]))tp --;
        else break;
    }
    stk[++ tp] = x;
}
int main()
{
    //freopen("data.in","r",stdin);
    scanf("%d",&n);
    Rep(i,n)scanf("%lf%lf",&l[i].a,&l[i].b),l[i].id = i;
    sort(l + 1,l + 1 + n,cmp);
    Rep(i,n)Ins(l[i]);
    Rep(i,tp)seq[i] = stk[i].id;
    sort(seq + 1,seq + 1 + tp);
    Rep(i,tp)printf("%d ",seq[i]);
    return 0;
}

随便写了几个题……
1046: [HAOI2007]上升序列
首先读错题了……TAT
实际上我们对于每个字典序都肯定是贪心的。
这样的话就随便搞了。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int N = 100001;
int bit[N],a[N],n,b[N],f[N],c[N];
int Qry(int x){int s = 0;for(;x <= n;x += x & -x)s = max(s,bit[x]);return s;}
void Upd(int x,int ff){for(;x;x -= x & -x)bit[x] = max(ff,bit[x]);}
void solve(int x)
{
    int cur = -1;
    for(int i = 1;i <= n;++ i)
    {
        if(f[i] >= x && x != 1 && c[i] > cur)
        {
            printf("%d ",c[i]);
            cur = c[i];
            x --;
        }
        else if(f[i] >= x && x == 1 && c[i] > cur)
        {
            printf("%d\n",c[i]);
            return ;
        }
    }
}
int main()
{
    scanf("%d",&n);
    Rep(i,n)scanf("%d",&a[i]),c[i] = a[i];
    Rep(i,n)b[i] = a[i];
    sort(b + 1,b + 1 + n);
    Rep(i,n)a[i] = lower_bound(b + 1,b + n + 1,a[i]) - b;//,printf("%d %d\n",c[i],a[i]);
    int mx = 0;
    for(int i = n;i;i --)
    {
        f[i] = Qry(a[i] + 1) + 1;
        //询问
        Upd(a[i],f[i]);
        //Update
        mx = max(f[i],mx);
    }
    int L,m;
    scanf("%d",&m);
    while(m --)
    {
        scanf("%d",&L);
        if(L <= mx)solve(L);
        else puts("Impossible");
    }
    return 0;
}


1047: [HAOI2007]理想的正方形
这个挺好玩的。
首先能想到单调队列,后来发现其实这个肯定是一维的扩展形态。
我们把列的滑动窗口计算出来之后,把他们合并起来,每次减少一个列就意味着每次减少这个列的两个最值。
这样的话二维的单调队列也显然了。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int N = 1001;
int n,m,K,gmax[N][N],gmin[N][N],a[N][N],Fmax[N][N],Fmin[N][N],f[N][N],ans,q[N];
void Init()
{
    for(int j = 1;j <= m;++ j)
    {
        int h = 1,t = 0;
        for(int i = 1;i <= K;++ i)
        {
            while(h <= t && a[q[t]][j] < a[i][j])t --;
            q[++ t] = i;
        }
        gmax[K][j] = a[q[h]][j];
        for(int i = K + 1;i <= n;++ i)
        {
            while(h <= t && i - q[h] >= K)++ h;
            while(h <= t && a[q[t]][j] <= a[i][j])t --;
            q[++ t] = i;
            gmax[i][j] = a[q[h]][j];
        }
        h = 1,t = 0;
        for(int i = 1;i <= K;++ i)
        {
            while(h <= t && a[q[t]][j] >= a[i][j])t --;
            q[++ t] = i;
        }
        gmin[K][j] = a[q[h]][j];
        for(int i = K + 1;i <= n;++ i)
        {
            while(h <= t && i - q[h] >= K)++ h;
            while(h <= t && a[q[t]][j] >= a[i][j])t --;
            q[++ t] = i;
            gmin[i][j] = a[q[h]][j];
        }
    }
}
void solve()
{
    ans = 1 << 30;
    for(int i = K;i <= n;++ i)
    {
        int h = 1,t = 0;
        for(int j = 1;j <= K;++ j)
        {
            while(h <= t && gmax[i][q[t]] <= gmax[i][j])t --;
            q[++ t] = j;
        }
        Fmax[i][K] = gmax[i][q[h]];
        for(int j = K + 1;j <= m;++ j)
        {
            while(h <= t && j - q[h] >= K)++ h;
            while(h <= t && gmax[i][q[t]] <= gmax[i][j])t --;
            q[++ t] = j;
            Fmax[i][j] = gmax[i][q[h]];
        }
        h = 1,t = 0;
        for(int j = 1;j <= K;++ j)
        {
            while(h <= t && gmin[i][q[t]] >= gmin[i][j])t --;
            q[++ t] = j;
        }
        Fmin[i][K] = gmin[i][q[h]];
        for(int j = K + 1;j <= m;++ j)
        {
            while(h <= t && j - q[h] >= K)++ h;
            while(h <= t && gmin[i][q[t]] >= gmin[i][j])t --;
            q[++ t] = j;
            Fmin[i][j] = gmin[i][q[h]];
        }
        for(int j = K;j <= m;++ j)f[i][j] = Fmax[i][j] - Fmin[i][j],ans = min(f[i][j],ans);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&K);
    Rep(i,n)Rep(j,m)scanf("%d",&a[i][j]);
    Init(); 
    solve();
    printf("%d\n",ans);
    return 0;
}

1050: [HAOI2006]旅行comf
这个题……
我只能对我的智商感到抱歉。
错误的思路:啊我可以二分答案。啊我可以枚举两个最边然后看存不存在一条路径。
好的我们来说正确的做法。
按照边权排序之后,从大到小加入边,这样的话我们可以保证最小值。而最大值可以用spfa求出。
然后就没有了……

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define v edge[i].to
#define RepG(i,x) for(int i = head[x];~ i;i = edge[i].next)
using namespace std;
const int N = 5001;
int L,R,head[501],cnt,S,T,n,m,dis[N],q[N * 20];
bool vis[501];
struct Edge{int next,to,w;}edge[N << 1];
struct E{int a,b,c;}e[N];
void save(int a,int b,int c){edge[cnt] = (Edge){head[a],b,c},head[a] = cnt ++;}
bool cmp(E x,E y){return x.c > y.c;}
int gcd(int x,int y){return !y ? x : gcd(y,x % y);}
int spfa()
{
    memset(dis,63,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[S] = 0;
    int h = 1,t = 1;
    q[1] = S;
    while(h <= t)
    {
        int x = q[h ++];
        RepG(i,x)
        {
            if(dis[v] > max(edge[i].w,dis[x]))
            {
                dis[v] = max(edge[i].w,dis[x]);
                if(!vis[v])vis[q[++ t] = v] = 1;
            }
        }
        vis[x] = 0;
    }
    return dis[T];
}
bool dfs(int x)
{
    if(x == T)return 1;
    vis[x] = 1;
    bool y = 0;
    RepG(i,x){if(!vis[v])y |= dfs(v);if(y)return 1;}
    return 0;
}
int main()
{
    //freopen("data.in","r",stdin);
    scanf("%d%d",&n,&m);
    cnt = 0;memset(head,-1,sizeof(head));
    Rep(i,m)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        e[i].a = a,e[i].b = b,e[i].c = c;
    }
    sort(e + 1,e + 1 + m,cmp);//从大向小加入
    scanf("%d%d",&S,&T);
    int a,b;
    double ans = 1e9;
    Rep(i,m)
    {
        save(e[i].a,e[i].b,e[i].c);save(e[i].b,e[i].a,e[i].c);
        int c = spfa();
        if(ans > (double)c / (e[i].c))
        {
            ans = (double)c / (e[i].c);
            a = e[i].c,b = c;
        }
    }
    if(!dfs(S))return puts("IMPOSSIBLE"),0;
    int g = gcd(a,b);
    if(g == a)printf("%d\n",b / a);
    else printf("%d/%d\n",b / g,a / g);
    return 0;
}


1037: [ZJOI2008]生日聚会Party
计数题还是需要做一些的啊。
感觉自己完全不会动态规划了已经……
考虑到,加入一个人只影响其后缀。
那么我们就考虑后缀中的最大的男女之差。
转移也就显然了……
f[i+1][j][p+1][max(q1,0)]+=f[i][j][p][q]
f[i][j+1][max(p1,0)][q+1]+=f[i][j][p][q]

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int mod = 12345678;
void Add(int &x,int y){x = (x + y) % mod;}
int n,m,K,f[152][152][22][22];
int lim(int x){return x > 0 ? x : 0;}
int main()
{
    scanf("%d%d%d",&n,&m,&K);
    f[0][0][0][0] = 1;
    for(int i = 0;i <= n;++ i)
    {
        for(int j = 0;j <= m;++ j)
        {
            for(int p = 0;p <= min(K,i);++ p)
            {
                for(int q = 0;q <= min(K,j);++ q)
                {
                    int st = f[i][j][p][q];
                    Add(f[i + 1][j][p + 1][lim(q - 1)],st);
                    Add(f[i][j + 1][lim(p - 1)][q + 1],st);
                }
            }
        }
    }
    int ans = 0;
    for(int p = 0;p <= K;++ p)for(int q = 0;q <= K;++ q)Add(ans,f[n][m][p][q]);
    printf("%d\n",ans);
    return 0;
}

1044: [HAOI2008]木棍分割
tmd就是个智障DP,结果数组开小调了我那么半天……
考虑到第一问直接二分答案。
然后第二问我们 f[i][j]=f[k][j1](sum[i]sum[k]>l)
j <script type="math/tex" id="MathJax-Element-54">j</script>这一维度直接滚动,注意到转移条件单调,我们直接开个队列就完事了。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
using namespace std;
const int N = 50003;
const int mod = 10007;
typedef long long LL;
int a[N],n,m,f[2][N],q[N],sum[N];
bool check(int x)
{
    int cur = 0,u = 0;
    for(int i = 1;i <= n;++ i)
    {
        if(a[i] > x)return 0;
        if(cur + a[i] > x)++ u,cur = a[i];
        else cur += a[i];
    }
    return u <= m;
}
void solve(int l)
{
    LL ans = 0;
    int lo = 0;
    f[0][0] = 1;
    for(int i = 1;i <= m;++ i)
    {
        int cur = 0;
        lo = i & 1;
        memset(f[lo],0,sizeof(f[lo]));
        cur = f[lo ^ 1][0];
        int h = 1,t = 1;
        q[1] = 0;
        for(int j = 1;j <= n;++ j)
        {
            while(h <= t && sum[j] - sum[q[h]] > l)cur = (cur - f[lo ^ 1][q[h ++]] + mod) % mod;
            q[++ t] = j;
            f[lo][j] = cur;
            cur = (cur + f[lo ^ 1][j]) % mod;
        }
        for(int j = n - 1;j;-- j)
        {
            if(sum[n] - sum[j] > l)break;
            ans = (ans + f[lo][j] + mod) % mod;
        }
    }
    printf("%lld\n",ans);
}
int main()
{
    scanf("%d%d",&n,&m);
    Rep(i,n)scanf("%d",&a[i]),sum[i] = sum[i - 1] + a[i];
    int l = 1,r = sum[n],mid;
    while(l < r)
    {
        mid = l + r >> 1;
        if(check(mid))r = mid;
        else l = mid + 1;
    }
    printf("%d ",l);
    solve(l);
    return 0;
}

1040: [ZJOI2008]骑士
给你个环套树森林。
求最大权值独立集。
这个东西显然跑网络流T飞了。
我们考虑这样的事情,对于环套树的环上的点,我们肯定是相邻的两个点只能选一个。
这样的话,我们任取环套树上的一条边,考虑这两个端点,我们分两种情况:
1.第一个不选,第二个随意。
2.第二个不选,第一个随意。
然后以这个不选的节点为根进行dfs。
注意到,如果我们这个节点不选,那么对于以后的节点的约束就全都去掉了。
那么就相当于把第一个节点连出的所有约束的边断掉了。
我们可以用dfs求出这条边的编号,在DP过程中不去看它。
这样的话我们就可以直接进行树形DP了。
注意到二元环的情况。
开始写的有问题,实际上我们相当于在存在二元环的时候不走这条边
并不代表不走(x -> y)的所有边。

#include <bits/stdc++.h>
#define Rep(i,n) for(int i = 1;i <= n;++ i)
#define v edge[i].to
#define RepG(i,x) for(int i = head[x];~ i;i = edge[i].next)
using namespace std;
const int N = 1000001;
typedef long long LL;
struct Edge{int next,to;}edge[N << 1];
struct P{int x;int y;}bel[N];
int head[N],cnt,fa[N],cur,n,val[N],Root,E,Flaj;
LL f[N],g[N];
bool ban[N],vis[N];
void save(int a,int b){edge[cnt] = (Edge){head[a],b},head[a] = cnt ++;}
int Find(int x){return fa[x] == x ? x : fa[x] = Find(fa[x]);} 
void Merge(int x,int y)
{
    if(Find(x) == Find(y))bel[++ cur].x = x,bel[cur].y = y;
    else fa[Find(x)] = Find(y);
}
void dfs(int x,int fa)
{
    g[x] = 0;
    f[x] = val[x];
    RepG(i,x)
    {
        if(x == Root && v == E && Flaj){Flaj = 0;continue;}
        if(v != fa && v != Root)
        {
            dfs(v,x);
            g[x] += max(g[v],f[v]);
            f[x] += g[v];
        }
    }
}
void Del(int x,int fa)
{
    vis[x] = g[x] = f[x] = 0;
    RepG(i,x)if(v != fa && v != Root)Del(v,x);
}
void solve()
{
    LL u = 0,ans = 0;
    int x;
    Rep(i,cur)
    {
        E = bel[i].x;
        Flaj = 1;
        dfs(Root = x = bel[i].y,0);
        u = g[x];
        Del(x,0);
        E = bel[i].y;
        Flaj = 1;
        dfs(Root = x = bel[i].x,0);
        u = max(u,g[x]);
        Del(x,0);
        ans += u;
    }
    printf("%lld\n",ans);
}
int main()
{
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    Rep(i,n)fa[i] = i;
    Rep(i,n)
    {
        int x;
        scanf("%d%d",&val[i],&x);
        save(i,x),save(x,i);
        Merge(x,i);
    }
    solve();
    return 0;
}
/*
环套树DP
考虑环上的一条边上的两个点,那么我们知道:
只能选一个。 
我们分别对这两个点进行DFS
强制不选另一个点即可
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值