[GD_SGOI 公路维护]块状链表、线段树无法维护的哦~

【题目】

提交文件:road.pas/.cpp

输入文件:road.in

输出文件:road.out

 

我们知道,每天都有成千上万的车辆在高速公路上行驶。如果一辆装有若干吨货物的卡车通过一段高速公路,就会对这段公路造成一定程度的破坏。

整段高速公路有一个初始的耐久度I。如果一辆装有d吨货物的卡车通过一段高速公路,这段公路的耐久度就会减少d。一旦某段公路的耐久度小于或等于0,这段公路就会永久性地毁坏。卡车无法通过已经毁坏的地方。

有两种维护公路的车辆:T1和T2。T1车可以将一段公路的耐久度增加r。T2车可以将一段公路的耐久度小于p的部分修复至p。虽然维护公路的车辆可以通过已经毁坏的部分,但是已经毁坏的地方仍然无法修复。

你的任务是统计一下一共有多少辆卡车可以成功通行。

 

输入格式:

第一行是三个整数NMI,表示高速公路的长度为N,有M辆车依次通过,整段高速公路的初始的耐久度为I

以下M行每行描述一辆车,格式如下:

1 s t d:一辆装有d吨货物的卡车准备通过区间[s,t]。在此之前先检查区间[s,t]是否完好。如果区间[s,t]里面有任何一个地方毁坏了,这辆卡车就会取消整个通行计划;否则这辆卡车就可以成功通过区间[s,t],即使通过之后会把某些地方毁坏。

2 s t r:一辆T1车通过区间[s,t],为这段公路的耐久度增加r

3 s t p:一辆T2车通过区间[s,t],为这段公路的耐久度修复至p

其中:1≤N≤100000,1≤M≤100000,1≤I≤1000,1≤st≤N,1≤d≤1000,1≤r≤1000,1≤p≤1000。

 

输出格式:

输出一个数,表示有多少辆卡车可以成功通行。

 

输入样例:

3 5 2

2 1 3 3

3 1 3 4

1 1 2 3

1 2 3 2

1 1 3 1

 

输出样例:

2

 

样例解释:

第3辆卡车无法通过区间[1,3]。因为虽然区间[1,2)和区间(2,3]的耐久度大于0,但是2这个点的耐久度为0,所以卡车无法通过。

【算法分析】
这个题就牛X了。。。至少对我来说。
块状链表嘛,就是把长度为n的整个数列分成sqrt(n)份,那么每份就有sqrt(n)个元素了。
然后维护三个标记。
1、key,表示这个块里最小的一个数是多少。
2、delta,这个块里的整体增值。
3、cc,这个块 < cc的要先变成cc。
4、low,这个块上一次标记清空之后,如果a[i]<=low,那么这个路就爆了。
然后我们设定cc都是在delta之前。那么维护上就有一点技巧了。
如果想不通的话就看程序吧。。。我觉得还是写得挺清晰地
= =
然后线段树的话,对于“重叠的标记”和“重叠标记”之间的合并无法做到。
于是无法完成这个任务。
【CODE】
#include #include #include #include #define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
const int N=100005;
struct Node{int l,r,key,delta,low,cc;}p[N/100];
int a[N],L;
int n,m,Stw,ans;

void build(){
for (int i=1;i<=n;i++) a[i]=Stw;
L=0;
int part=(int)(sqrt(n));
if (part==0) part=1;
for (int i=1;(i-1)*part+1<=n;i++){
L=i;
p[i].l=(i-1)*part+1;
p[i].r=min(i*part,n);
p[i].key=Stw;
p[i].delta=p[i].low=p[i].cc=0;
}
}

void push(int i){
for (int j=p[i].l;j<=p[i].r;j++){
if (a[j]<=p[i].low){
a[j]=0;
continue;
}
a[j]>?=p[i].cc;
a[j]+=p[i].delta;
}
}

void remake(int i){
p[i].delta=p[i].low=p[i].cc=0;
p[i].key=0x7FFFFFFF;
for (int j=p[i].l;j<=p[i].r;j++)
p[i].key}

void Deal1(int l,int r,int w){
int i,j;
for (i=1;i<=L;i++){
if (r if (l<=p[i].l && p[i].r<=r){
if (p[i].key<=0) return;
continue;
}
push(i);
remake(i);
for (j=max(l,p[i].l);j<=min(r,p[i].r);j++)
if (a[j]<=0) return;
}
ans++;
for (i=1;i<=L;i++){
if (r if (l<=p[i].l && p[i].r<=r){
p[i].key-=w;
p[i].delta-=w;
if (p[i].cc+p[i].delta<=0) p[i].low>?=(-p[i].delta);
continue;
}
push(i);
for (j=max(l,p[i].l);j<=min(r,p[i].r);j++) a[j]-=w;
remake(i);
}
}

void Deal2(int l,int r,int w){
int i,j;
for (i=1;i<=L;i++){
if (r if (l<=p[i].l && p[i].r<=r){
if (p[i].key>0) p[i].key+=w;
p[i].delta+=w;
continue;
}
push(i);
for (j=max(l,p[i].l);j<=min(r,p[i].r);j++)
if (a[j]>0) a[j]+=w;
remake(i);
}
}

void Deal3(int l,int r,int w){
int i,j;
for (i=1;i<=L;i++){
if (r if (l<=p[i].l && p[i].r<=r){
if (p[i].key>0) p[i].key>?=w;
p[i].cc>?=w-p[i].delta;
continue;
}
push(i);
for (j=max(l,p[i].l);j<=min(r,p[i].r);j++)
if (a[j]>0) a[j]>?=w;
remake(i);
}
}

void solve(){
int i,op,l,r,w;
scanf("%d%d%d",&n,&m,&Stw);
build();
ans=0;
for (i=1;i<=m;i++){
scanf("%d%d%d%d",&op,&l,&r,&w);
switch (op){
case 1:
Deal1(l,r,w);
break;
case 2:
Deal2(l,r,w);
break;
case 3:
Deal3(l,r,w);
break;
}
}
printf("%dn",ans);
}

int main(){
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
solve();
}

[GD_SGOI 摘取作物]费用流

【题目】

提交文件:pick.pas/.cpp

输入文件:pick.in

输出文件:pick.out

 

Feather的农场里有N*M块地,排列成N行,每行M块地。Feather在每块地里种植了不同的农作物。现在这些农作物都成熟了,可以摘取下来出售了。其中第i行第j列的地里的农作物的价值为W[i,j]

JackRabbit是Feather的好友,平时经常为Feather的农作物除草除虫。为了答谢JackRabbit,Feather决定把一部分农作物送给JackRabbit。JackRabbit很高兴,恨不得一下子把农场里的农作物摘空。

为了防止JackRabbit把农作物摘空,Feather提出了两个条件:

1.每行最多选取两块地;

2.每列最多选取两块地。

  这下子把JackRabbit难住了。如何在满足这两个条件的前提下,使得摘取的农作物的价值之和最大呢?

 

输入格式:

  第一行是两个整数(3≤N≤30,3≤M≤30)。

  以下N行每行M个整数,表示每块地的农作物的价值W[i,j](0≤W[i,j]≤10000)。

 

输出格式:

  输出一个数,表示在满足条件的前提下摘取的农作物的价值之和的最大值。

 

输入样例:

3 3

1 5 3

4 7 5

0 4 1

 

输出样例:

21

 

样例解释:

第一行选5和3,第二行选4和5,第三行选0和4,总和为21,是满足条件的最佳选取方案。

【算法分析】
本题是这套题里最脑残的题目。
就是二分图。左边的点是行,右边的点是列。
中间连容量为1,费用为a[i][j]的边。
左右两边与源汇连容量为2的边。
但是直接这样求最大费用最大流是会错误的。
为什么呢?
因为最大流不一定取得最大费用。
最大费用最大流是保证流量最大的情况下取得的费用最大。
于是我们需要将二分图左边的点都向汇连容量为2的边。这要可以允许“不选满”。
然后最后的流量必定是2*n (n是二分图左边的点数)。
又能保证费用最大。
【其它】
在交卷之前,我对我可爱的徒弟小肥闽曰:“你第三题不要错了啊~”
肥闽答曰:“第三题错了我自切JJ!”
然后,他果断错了1个点,就是因为没有处理上述那种情况,于是果断SB。要是数据再黑一点果断要挂。。。。
【CODE】
#include #include #include const int N=30005;
const int INF=1000000000;
struct edge{int x,y,c,w;edge *next,*op;}*ls[N],*fa[N];
int m,n,vs,vt,cost;
int d[N],Q[N],v[N];

void addedge(int x,int y,int c,int w){
edge *p=(edge*)malloc(sizeof(edge));
edge *q=(edge*)malloc(sizeof(edge));
p->x=x; p->y=y; p->c=c; p->w= w; p->next=ls[x]; ls[x]=p; p->op=q;
q->x=y; q->y=x; q->c=0; q->w=-w; q->next=ls[y]; ls[y]=q; q->op=p;
}

void init(){
int i,j,x;
scanf("%d%d",&m,&n);
for (i=1;i<=m;i++)
for (j=1;j<=n;j++){
scanf("%d",&x);
addedge(i,m+j,1,x);
}
vs=0; vt=m+n+1;
for (i=1;i<=m;i++){
addedge(vs,i,2,0);
addedge(i,vt,2,0);
}
for (i=1;i<=n;i++) addedge(m+i,vt,2,0);
}

bool spfa(){
int head=0,tail=1;
for (int i=vs;i<=vt;i++){
d[i]=-INF;
v[i]=0;
}
d[vs]=0; v[vs]=1; Q[1]=vs;
while (head!=tail){
head++; if (head>=N) head=0;
for (edge *t=ls[Q[head]];t;t=t->next)
if (t->c && d[t->x]+t->w>d[t->y]){
d[t->y]=d[t->x]+t->w;
fa[t->y]=t;
if (!v[t->y]){
v[t->y]=1;
tail++; if (tail>=N) tail=0;
Q[tail]=t->y;
}
}
v[Q[head]]=0;
}
if (d[vt]<=0) return false;
return true;
}

void change(){
int nf=INF;
for (int i=vt;i!=vs;i=fa[i]->x)
if (fa[i]->c cost+=nf*d[vt];
for (int i=vt;i!=vs;i=fa[i]->x){
fa[i]->c-=nf;
fa[i]->op->c+=nf;
}
}

void MCMF(){
cost=0;
while (spfa()) change();
printf("%dn",cost);
}

int main(){
freopen("pick.in","r",stdin);
freopen("pick.out","w",stdout);
memset(ls,0,sizeof(ls));
init();
MCMF();
}

[GD_SGOI 数字贴吧]AC自动机上的动态规划、高精度

【题目】

提交文件:num.pas/.cpp

输入文件:num.in

输出文件:num.out

 

在百度贴吧里输入关键词,就可以进入相应的贴吧参与该话题的讨论。然而,有一些关键词是不能作为贴吧名称的。我们把这些关键词称为敏感词。

如果一个字符串的任何子串(包括本身)都不是敏感词,那么这个字符串才可以作为贴吧名称。

有一些贴吧是直接用数字作为贴吧名称的。例如:“0”、“1”、“100”、“0123456789”。然而,数字里面也有敏感词。如果“00”是敏感词,则包含连续2个“0”的数字都不得作为贴吧名称。

你的任务是统计一下一共有多少个数字可以作为贴吧名称。

 

输入格式:

第一行是,表示贴吧名称的最大长度为N≤63),一共有M≤10)个数字是敏感词。

以下M行每行一个数字,长度都不超过5。

 

输出格式:

输出可以作为贴吧名称的数字个数。

 

输入样例:

3 10

2

3

4

5

6

7

8

9

10

11

 

输出样例:

6

 

样例解释:

只有“0”、“1”、“00”、“01”、“000”、“001”可以作为贴吧名称。

【算法分析】
很容易发现。就是AC自动机dp,貌似又叫DFA?
然后对给出的串建立AC自动机。
于是F[i][j]表示已建立的串长度为i,在自动机中的j状态,所能有的方案数。
然后转移的话,就是在自动机的每个状态那里,加一个0~9的字符,然后给“转移以后的状态”加上当前这个状态的方案数。(加法原理。。。)
但是“推出方案数”的状态不能是一个串的结尾,因为按题意不能匹配!

最后的答案就是所有F[i][j]之和。当然,j不能是一个串的结尾。
最后由于这题实在太不厚道,还要用高进度写F[i][j]。。。
太猥琐了。。。
【CODE】
#include #include #include const int N=55;
int n,m,tot,Q[N];
struct Trie_t{int son[10];int Final,next;}Tr[N];
int F[70][55][101];
char str[N];

void Insert(){
int L=strlen(str);
int p=1;
for (int i=0;i if (Tr[p].son[str[i]-‘0’]) p=Tr[p].son[str[i]-‘0’];
else{
tot++;
Tr[p].son[str[i]-‘0’]=tot;
p=tot;
}
Tr[p].Final++;
}

void init(){
tot=1;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%s",str);
Insert();
}
}

void makenext(){
int head=0,tail=1;
Q[1]=1;
while (head!=tail){
head++;
int &p=Q[head];
if (Tr[Tr[p].next].Final) Tr[p].Final=1;
for (int c=0;c<10;c++)
if (Tr[p].son){
int q=Tr[p].next;
while (q && !Tr[q].son) q=Tr[q].next;
if (q) Tr[Tr[p].son].next=Tr[q].son;
else Tr[Tr[p].son].next=1;
Q[++tail]=Tr[p].son;
}
}
}

void plus(int *A,int *B){
for (int i=1;i<=100;i++) A[i]+=B[i];
for (int i=100;i>1;i–){
A[i-1]+=A[i]/10;
A[i]%=10;
}
}

void output(int *A){
int i,j;
for (i=1;i<=100;i++)
if (A[i]){
for (j=i;j<=100;j++) printf("%d",A[j]);
printf("n");
return;
}
printf("0n");
}

void dp(){
memset(F,0,sizeof(F));
F[0][1][100]=1;
for (int L=0;L for (int i=1;i<=tot;i++) if (!Tr[i].Final)
for (int j=0;j<10;j++){
int p=i;
while (p && !Tr[p].son[j]) p=Tr[p].next;
if (!p) plus(F[L+1][1],F[L][i]);
else plus(F[L+1][Tr[p].son[j]],F[L][i]);
}
int ans[101];
memset(ans,0,sizeof(ans));
for (int i=1;i<=n;i++)
for (int j=1;j<=tot;j++) if (!Tr[j].Final)
plus(ans,F[i][j]);
output(ans);
}

int main(){
freopen("num.in","r",stdin);
freopen("num.out","w",stdout);
init();
makenext();
dp();
}

[GD_SGOI 三角阵]找规律、字符串变换

【题目】

提交文件:tri.pas/.cpp

输入文件:tri.in

输出文件:tri.out

 

  把3个相同的小三角形按如下方式连接起来就形成了一个一级三角阵。

  我们把位于顶端的小三角形标记为T,位于左端的小三角形标记为L,位于右端的小三角形标记为R。

  把3个一级三角阵按同样的方式连接起来就形成了一个二级三角阵。

  我们为顶端的三角阵的标记添加前缀T,为左端的三角阵的标记添加前缀L,为右端的三角阵的标记添加前缀R。

把3个二级三角阵按同样的方式连接起来就形成了一个三级三角阵。

同样地为顶端的三角阵的标记添加前缀T,为左端的三角阵的标记添加前缀L,为右端的三角阵的标记添加前缀R。

依次类推,可以构建一个N级三角阵。

如果两个小三角形有公共点,则认为这两个小三角形相邻,可以一步到达。

你的任务是求从一个小三角形走到另一个小三角形至少需要多少步。

 

输入格式:

第一行是三角阵的等级≤30)。

第二行和第三行都是一个长度为N的字符串,由大写字母“L”、“R”、“T”组成,表示两个小三角形的标号。

 

输出格式:

输出一个数,表示从一个小三角形走到另一个小三角形所需的最少步数。

 

输入样例:

3

TRL

RLR

 

输出样例:

5

 

样例解释:

从“TRL”出发,途经“TRR”、“RTT”、“RTL”、“RLT”,最后到达“RLR”,一共需要5步。

【算法分析】
首先我们围观它走一步的话,那个字符串会有什么变化。
第一种可能:"abc"->"abk" 然后这个这个c和k是可以任意的。
第二种可能:"xyzabbbbbbb…bbbbb"->"xyzbaaaaaaa…aaaaa",当然,这个a和b不能相同。
于是可以很直接地得到一个走到本三角顶点的算法。
那就是从高位到低位,一位一位地处理,具体自己YY吧,不难。
并且容易发现他是最优的。(怎么发现?看图。。。)
然后对于A串和B串,我们先把相同的前缀删掉。
对于剩下的东西而言。有两种可能。
剩下的设长度为n。
1、从我所属于的这个(n-1)级三角形直接走到对象的那个(n-1)级三角形。
2、从我所属于的这个(n-1)级三角形绕路另一个(n-1)级三角形,再跑到对象的那个(n-1)三角形。
于是分开两种情况,min一下即可。
【CODE】
#include #include #include typedef __int64 lld;
const int N=55;
int n;
char str1[N],str2[N],s1[N],s2[N];

lld solve(char *ss1,char *ss2){
memcpy(s1,ss1,sizeof(s1));
memcpy(s2,ss2,sizeof(s2));
int i,j,k;
lld ans=0;
for (i=1;i<=n;i++)
if (s1[i]!=s2[i]){
for (j=n;j>i;j–)
if (s2[i]!=s1[j]){
ans+=(lld)(1)<<(n-j);
s1[j]=s2[i];
}
ans++;
for (j=n;j>i;j–) s1[j]=s1[i];
s1[i]=s2[i];
}
return ans;
}

void work(){
if (strcmp(str1+1,str2+1)==0) puts("0");
else{
lld ans=solve(str1,str2);
char str[N];
memcpy(str,str2,sizeof(str));
int i=1,j;
while (str1[i]==str2[i]) i++;
if (str1[i]==’T’){
if (str2[i]==’L’) str[i]=’R’;
if (str2[i]==’R’) str[i]=’L’;
}
if (str1[i]==’L’){
if (str2[i]==’T’) str[i]=’R’;
if (str2[i]==’R’) str[i]=’T’;
}
if (str1[i]==’R’){
if (str2[i]==’L’) str[i]=’T’;
if (str2[i]==’T’) str[i]=’L’;
}
for (j=i+1;j<=n;j++) str[j]=str1[i];
lld now=solve(str1,str)+solve(str,str2);
if (now printf("%I64dn",ans);
}
}

int main(){
freopen("tri.in","r",stdin);
freopen("tri.out","w",stdout);
scanf("%d%s%s",&n,str1+1,str2+1);
work();
return 0;
}

[POI2005]Kos-Dicing【二分答案+最大流】

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1532
【算法分析】
二分答案然后,将比赛分配给选手,判定能不能分完。
这个用最大流实现。
【其它】sap被卡。HLPP被卡。。。于是dinic。。。
于是你可以见识到什么叫卡过。。。
34 30276 edward_mj 3164K 4772MS G++ 2.39K 2010-06-16 01:39:26 【CODE】
#include #include #include #define min(x,y) ((x)<(y)?(x):(y))
const int N=20005;
const int INF=0x7FFFFFFF;
struct edge{int x,y,c;edge *next,*op;}*ls[N],*cur[N],*fa[N];
int n,m,vs,vt,Flow;
int d[N],lx[N],ly[N],v[N],Q[N];

void init(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d%d",&lx[i],&ly[i]);
vs=0; vt=n+m+1;
}

inline void addedge(int x,int y,int c){
edge *p=(edge*)malloc(sizeof(edge));
edge *q=(edge*)malloc(sizeof(edge));
p->x=x; p->y=y; p->c=c; p->next=ls[x]; ls[x]=p; p->op=q;
q->x=y; q->y=x; q->c=0; q->next=ls[y]; ls[y]=q; q->op=p;
}

bool bfs(){
for (int i=vs;i<=vt;i++){
d[i]=INF;
v[i]=0;
cur[i]=ls[i];
}
d[vs]=1; Q[1]=vs;
for (int st=1,ed=1;st<=ed;st++)
for (edge *t=ls[Q[st]];t;t=t->next)
if (t->c && d[t->y]==INF){
d[t->y]=d[t->x]+1;
Q[++ed]=t->y;
if (t->y==vt) return true;
}
return false;
}

void change(){
int nf=INF;
for (int i=vt;i!=vs;i=fa[i]->x)
if (fa[i]->c for (int i=vt;i!=vs;i=fa[i]->x){
fa[i]->c-=nf;
fa[i]->op->c+=nf;
}
Flow+=nf;
}

void dinic(){
int i;
while (bfs()){
i=vs;
while (cur[vs]){
edge *&t=cur[i];
for (;t;t=t->next)
if (t->c && d[t->x]+1==d[t->y] && !v[t->y]) break;
if (t){
fa[t->y]=t;
i=t->y;
if (i==vt){
change();
i=vs;
}
}else{
v[i]=1;
if (i!=vs) i=fa[i]->x;
}
}
}
}

bool Can(int Limit){
for (int i=vs;i<=vt;i++)
while (ls[i]){
edge*t=ls[i];
ls[i]=t->next;
free(t);
}
for (int i=1;i<=m;i++){
addedge(vs,i,1);
addedge(i,m+lx[i],1);
addedge(i,m+ly[i],1);
}
for (int i=1;i<=n;i++) addedge(m+i,vt,Limit);
Flow=0;
dinic();
if (Flow==m) return true;
return false;
}

void solve(){
int l=m/n,r=m,mid;
while (l mid=(l+r)/2;
if (Can(mid)) r=mid;
else l=mid+1;
}
printf("%dn",l);
}

int main(){
init();
solve();
}