2016HDU多校赛第4场(hdu5765 hdu5769 hdu5772)

hdu5765 Bonds

题意

给你一个连通图,问每个边在多少个极小割边集上。

解法

官方题解:

极小割边集的定义下割边集恰好会将原图分成两块。枚举左右两块是否独立连通。块连通必然可以删掉一个点,这个点与剩下的点有边且剩下的点连通,可以先状压每个点的边, DP转移即可。
接下来要处理的是将两个块之间的边加一。这部分可以处理成将左块中的边减一,右块中的边减一,整体加一。操作变成了把一个块的边加权值的操作。考虑一条边表达成形如 0..010..010..00..010..010..0的形式,它在所有块中的权值总和,便是求一遍把这个形式作为子集的集合的权值和,求一遍高维前缀和就可以了。

大神们总是说随便写写就过,但是并不能看懂题解说的是什么,看了好久标程(只想吐槽能或的地方为何要异或,很影响理解)

先预处理出来每种点集周围的可达的点的集合,然后做一次bfs处理出所有可以构成连通图的点集。

枚举一个点集i,令j = U - i,看i和j是否是连通的,若是,则记录sum[i]++;sum[j]++,ans++

但是i和j求的是以当前割集分割的情况下,i和j的极大的情况的sum值,子连通图其实可以由i和j转移过来,但是为了避免记重,要用到一个高维前缀和

最后答案就是:

(连通的点集总数 - 包含第i条边(u,v)的连通点集数目) / 2

高维前缀和

就是要枚举转移第几位,每次按位从父状态转移,这样可以保证在父状态指向相同祖先状态时不会重复计数。

代码

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

using namespace std;

const int SIZE = 22;
int rch[1<<SIZE], sum[1<<SIZE], q[1<<SIZE], u[405], v[405];
bool show[1<<SIZE];
inline int lb(int x) {return x&(-x);}

int main() {
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++) {
        int n, m;
        scanf("%d%d",&n,&m);
        fill(rch , rch + (1<<n) , 0);
        fill(show , show + (1<<n) , 0);
        fill(sum , sum + (1<<n) , 0);
        for(int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d",&u,&v);
            ::u[i] = u;
            ::v[i] = v;
            rch[1<<u] |= 1<<v;
            rch[1<<v] |= 1<<u;
        }
        for(int i = 1; i < 1<<n; i++) {
            rch[i] |= rch[i-lb(i)] | rch[lb(i)]; //处理出当前点集临接的点
        }
        int l = 0, r = 0;
        for(int i = 0; i < n; i++) show[q[r++] = 1<<i] = true;
        //标程上学来的神奇bfs
        while(l < r) {
            int c = q[l++]; //当前选的点集
            int left = rch[c] ^ (rch[c] & c); //剩下可达的没去的点集
            while(left) {
                int now = lb(left) | c;
                if(!show[now]) show[q[r++] = now] = true;
                left -= lb(left);
            }
        } 
        int all = 0;
        for(int i = 0; i < 1 << n; i++) {
            int j = (1 << n) - 1;
            j ^= i;
            if(show[i] && show[j]) {
                sum[i]++;
                all++; 
                sum[j]++;
            }
        }
        //all >>= 1; 
        for(int j = 0; j < n; j++) {
            for(int i =(1<<n)-1;i>=0;--i) if(!(i>>j&1)) sum[i] += sum[i^(1<<j)]; 
            //高维前缀和,sum[i]代表点i超集数目,即包含i的连通的点集数目
        }
        printf("Case #%d: ",cs);
        for(int i = 0; i < m; i++) {
            printf("%d%c",(all - sum[(1<<u[i]) | (1<<v[i])])/2, " \n"[i+1==m]);
            //all是所有连通点集数目,减去同时包含u和v的连通点集个数,剩下就是以(u,v)为割的点集数目的两倍
        }
    }

}

hdu5769 Substring

题意

求包含字符x的不同子串个数

解法

后缀数组板题。
不同子串个数是 ∑length−(sa[i]+height[i])
带限制字符x时只要判断对于后缀sa[i]最近的x位置(没有时设为n)和sa[i]+height的最大值就好了

代码

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
typedef long long ll;
const int MAXN=100010;
int t1[MAXN],t2[MAXN],c[MAXN];//求SA数组需要的中间变量,不需要赋值
//待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
//除s[n-1]外的所有s[i]都大于0,r[n-1]=0
//函数结束以后结果放在sa数组中
//sa[1~n]->[0,N] rank[0~n-1]->[1,N]  height[1~n]
bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int Rank[],int height[],int n,int m)
{
    n++;
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s的最大值很大,可改为快速排序
    for(i = 0;i < m;i++)c[i] = 0;
    for(i = 0;i < n;i++)c[x[i] = str[i]]++;
    for(i = 1;i < m;i++)c[i] += c[i-1];
    for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1)
    {
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i = n-j; i < n; i++)y[p++] = i;//后面的j个数第二关键字为空的最小
        for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i = 0; i < m; i++)c[i] = 0;
        for(i = 0; i < n; i++)c[x[y[i]]]++;
        for(i = 1; i < m;i++)c[i] += c[i-1];
        for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];
        //根据sa和x数组计算新的x数组
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(i = 1;i < n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p >= n)break;
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++)Rank[sa[i]] = i;
    for(i = 0;i < n;i++)
    {
        if(k)k--;
        j = sa[Rank[i]-1];
        while(str[i+k] == str[j+k])k++;
        height[Rank[i]] = k;
    }
}
int Rank[MAXN],height[MAXN];

char str[MAXN];
char X[3];
int r[MAXN];
int sa[MAXN];
int nxt[MAXN];

int main(){
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++) {
        scanf("%s%s",X,str);
        int n = strlen(str);
        int temp = n;
        for(int i = n - 1; i >= 0; i--) {
            if(str[i] == X[0]) temp = i;
            nxt[i] = temp;

        }
        for(int i = 0; i < n; i++) r[i] = str[i];
        r[n] = 0;
        da(r,sa,Rank,height,n,128);
        ll ans = 0;
        for(int i = 1; i <= n; i++) {
            //printf("%d %d %d\n",nxt[sa[i]], sa[i] , height[i]);
            ans += n - max(nxt[sa[i]], sa[i] + height[i]);
        }
        printf("Case #%d: %I64d\n",cs,ans);
    }
    return 0;
}

hdu5772

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5772

解法

最大权闭合子图问题。(正点权之和 - 最大流)
难点在建图,首先将点分为3类

第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于w[i][j]+w[j][i](表示得分)

第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费)

第三类:对于10种字符拆出10个点,每个点的权值为 -(b[x]-a[x])

然后跑一个dinic就行了

代码

/*
最大流模板
dinic算法
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=15000;//点数的最大值
const int MAXM=1050000;//边数的最大值

struct Node
{
    int from,to,next;
    int cap;
}edge[MAXM];
int tol;

int dep[MAXN];//dep为点的层次
int head[MAXN];

int n;
void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)//第一条变下标必须为偶数
{
    //printf("u=%d v=%d w=%d\n",u,v,w);
    edge[tol].from=u;
    edge[tol].to=v;
    edge[tol].cap=w;
    edge[tol].next=head[u];
    head[u]=tol++;
    edge[tol].from=v;
    edge[tol].to=u;
    edge[tol].cap=0;
    edge[tol].next=head[v];
    head[v]=tol++;
}

int BFS(int start,int end)
{
    int que[MAXN];
    int front,rear;
    front=rear=0;
    memset(dep,-1,sizeof(dep));
    que[rear++]=start;
    dep[start]=0;
    while(front!=rear)
    {
        int u=que[front++];
        if(front==MAXN)front=0;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(edge[i].cap>0&&dep[v]==-1)
            {
                dep[v]=dep[u]+1;
                que[rear++]=v;
                if(rear>=MAXN)rear=0;
                if(v==end)return 1;
            }
        }
    }
    return 0;
}
int dinic(int start,int end)
{
    int res=0;
    int top;
    int stack[MAXN];//stack为栈,存储当前增广路
    int cur[MAXN];//存储当前点的后继
    while(BFS(start,end))
    {
        memcpy(cur,head,sizeof(head));
        int u=start;
        top=0;
        while(1)
        {
            if(u==end)
            {
                int min=INF;
                int loc;
                for(int i=0;i<top;i++)
                  if(min>edge[stack[i]].cap)
                  {
                      min=edge[stack[i]].cap;
                      loc=i;
                  }
                for(int i=0;i<top;i++)
                {
                    edge[stack[i]].cap-=min;
                    edge[stack[i]^1].cap+=min;
                }
                res+=min;
                top=loc;
                u=edge[stack[top]].from;
            }
            for(int i=cur[u];i!=-1;cur[u]=i=edge[i].next)
              if(edge[i].cap!=0&&dep[u]+1==dep[edge[i].to])
                 break;
            if(cur[u]!=-1)
            {
                stack[top++]=cur[u];
                u=edge[cur[u]].to;
            }
            else
            {
                if(top==0)break;
                dep[u]=-1;
                u=edge[stack[--top]].from;
            }
        }
    }
    return res;
}

int a[15], b[15], w[105][105];
char s[105];

int main()//多源多汇点,在前面加个源点,后面加个汇点,转成单源单汇点
{
    int start,end;
    int u,v,z;
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++)
    {
        init();
        scanf("%d",&n);
        scanf("%s",s);
        end = n*n + n + 10 + 1;
        start = 0;
        int res = 0;
        for(int i = 0; i <= 9; i++) {
            scanf("%d %d",&a[i],&b[i]);
        }
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                scanf("%d",&w[i][j]);
            }
        }
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(i == j) continue;
                int u = i * n + j + 1;
                res += w[i][j];
                addedge(0,u,w[i][j]);
                addedge(u,n*n+i+1,INF);
                addedge(u,n*n+j+1,INF);
            }
        }
        //printf("-----%d\n",res);
        for(int i = 0; i < n; i++) {
            addedge(n*n+i+1, end, a[s[i] - '0']);
            addedge(n*n+i+1, s[i] - '0' + 1 + n*n + n, INF);
        }
        for(int i = 0; i < 10; i++) {
            addedge(i + 1 + n*n +n, end, b[i] - a[i]);
        }
        int ans=dinic(start,end);
        printf("Case #%d: %d\n",cs,res - ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值