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;
}