[NOI2005][Day1]维护数列(sequence)

此题可以说是NOI历史上最繁杂的数据结构题目之一。出题者周源给出的标准解法为块状链表,他的标程长达11.3KB之多!块状链表虽然可以完美解决此题,但是其代码实现比较复杂,很容易出错。而Splay这种非常优美的数据结构可以巧妙地解决这道题,且其代码实现相对容易。

从零开始学习Splay,请参见sqybi的“The Magical Splay”。已经掌握Splay基础知识的,可以阅读zkw神牛的 《BST 拓展与伸展树(splay)一日通》 学习很多代码实现上的技巧,尤其是设立一个空结点null来避开一大堆繁杂的边界情况特殊判断。

此题用Splay的详细解法请参见BYVoid的解题报告 。我的代码大致与BYVoid的思路相符,但在插入操作时我并没有插成一条链,而是递归将要插入的子序列建成一颗比较平衡的子树。实际测试中,我的代码时间效率比较高,与块状链表标程的用时不相上下,甚至在极限大数据下比标程快。如果用fread快速读入方式,还可以节约近0.5s时间。

评测结果:
我的Splay代码
Start testing gnarlycow…
Compiling gnarlycow.sequence …
Success
Testing gnarlycow.sequence.1(sequence1.in)… Accepted 0.003s Score: 10
Testing gnarlycow.sequence.2(sequence2.in)… Accepted 0.007s Score: 10
Testing gnarlycow.sequence.3(sequence3.in)… Accepted 0.878s Score: 10
Testing gnarlycow.sequence.4(sequence4.in)… Accepted 0.149s Score: 10
Testing gnarlycow.sequence.5(sequence5.in)… Accepted 0.402s Score: 10
Testing gnarlycow.sequence.6(sequence6.in)… Accepted 0.780s Score: 10
Testing gnarlycow.sequence.7(sequence7.in)… Accepted 1.644s Score: 10
Testing gnarlycow.sequence.8(sequence8.in)… Accepted 1.590s Score: 10
Testing gnarlycow.sequence.9(sequence9.in)… Accepted 1.038s Score: 10
Testing gnarlycow.sequence.10(sequence10.in)… Accepted 1.190s Score: 10
Total score for gnarlycow.sequence : 100
块状链表标程:
Start testing standard…
Compiling standard.sequence …Success
Testing standard.sequence.1(sequence1.in)… Accepted 0.002s Score: 10
Testing standard.sequence.2(sequence2.in)… Accepted 0.010s Score: 10
Testing standard.sequence.3(sequence3.in)… Accepted 0.665s Score: 10
Testing standard.sequence.4(sequence4.in)… Accepted 0.362s Score: 10
Testing standard.sequence.5(sequence5.in)… Accepted 0.656s Score: 10
Testing standard.sequence.6(sequence6.in)… Accepted 0.931s Score: 10
Testing standard.sequence.7(sequence7.in)… Accepted 1.475s Score: 10
Testing standard.sequence.8(sequence8.in)… Accepted 1.879s Score: 10
Testing standard.sequence.9(sequence9.in)… Accepted 1.669s Score: 10
Testing standard.sequence.10(sequence10.in)… Accepted 1.778s Score: 10
Total score for standard.sequence : 100

/* Problem: NOI2005 Day1 sequence
 * Author: Li, Peiqian
 * Time: 2010.06.30
 * Status: AC
 * Tags: Splay
 */
#include 
#include 

using namespace std;

int num[500010],ptr=0,sptr=0;
struct node *null,*root,*s[500010],*q[500010];
struct node
{
	int value,size,sum,lmax,rmax,mmax;
	bool rev,same;
	node *c[2],*father;

	inline void reverse()
	{
		if(this==null) return;
		swap(c[0],c[1]);
		swap(lmax,rmax);
		rev=!rev;
	}

	inline void makesame(int v)
	{
		if(this==null) return;
		value=v;
		sum=v*size;
		lmax=rmax=mmax=max(sum,value);
		same=true;
	}

	inline void propagate()
	{
		if(this==null) return;
		if(rev)
		{
			c[0]->reverse();
			c[1]->reverse();
			rev=false;
		}
		if(same)
		{
			c[0]->makesame(value);
			c[1]->makesame(value);
			same=false;
		}
	}

	inline void update()
	{
		if(this==null) return;
		size=c[0]->size+c[1]->size+1;
		sum=c[0]->sum+c[1]->sum+value;
		lmax=max(c[0]->lmax,c[0]->sum+value+max(0,c[1]->lmax));
		rmax=max(c[1]->rmax,c[1]->sum+value+max(0,c[0]->rmax));
		mmax=max(c[0]->mmax,c[1]->mmax);
		mmax=max(mmax,max(0,c[0]->rmax)+value+max(0,c[1]->lmax));
	}

	inline void rotate(int o)//zig: o=0; zag: o=1;
	{
		node *t=father;
		t->propagate();
		propagate();
		t->c[!o]=c[o];
		father=t->father;
		if(c[o]!=null) c[o]->father=t;
		if(t->father!=null) t->father->c[t->father->c[1]==t]=this;
		t->father=this;
		c[o]=t;
		if(t==root) root=this;
		t->update();
	}

	inline void splay(node *goal)
	{
		for(propagate();father!=goal;)
			if(father->father==goal) rotate(father->c[0]==this);
			else
			{
				int o=(father->father->c[0]==father);
				if(father->c[!o]==this)
				{
					father->rotate(o);
					rotate(o);
				}
				else
				{
					rotate(!o);
					rotate(o);
				}
			}
		update();
	}

	inline void clear()
	{
		if(this==null) return;
		q[0]=this;
		for(int head=0,tail=1;headc[0]!=null) q[tail++]=q[head]->c[0];
			if(q[head]->c[1]!=null) q[tail++]=q[head]->c[1];
		}
	}
} tree[500010];

inline node* newnode(int x)
{
	node *p=sptr?s[--sptr]:tree+(ptr++);
	p->value=p->sum=p->lmax=p->rmax=p->mmax=x;
	p->size=1;
	p->father=p->c[0]=p->c[1]=null;
	p->rev=p->same=false;
	return p;
}

inline void select(int pos,node *goal)
{
	node *t=root;
	for(;;)
	{
		t->propagate();
		int m=t->c[0]->size;
		if(pos<=m) t=t->c[0];
		else if(pos==m+1) break;
		else
		{
			t=t->c[1];
			pos-=m+1;
		}
	}
	t->splay(goal);
}

node* maketree(int L,int R,node *f)
{
	if(L==R) return null;
	int M=(L+R)>>1;
	node *p=newnode(num[M]);
	p->c[0]=maketree(L,M,p);
	p->c[1]=maketree(M+1,R,p);
	p->father=f;
	p->update();
	return p;
}

inline void insertnum(int pos,int tot)
{
	for(int i=0;ic[1]->c[0]=maketree(0,tot,root->c[1]);
	root->c[1]->update();
	root->update();
}

int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	int n,m,pos,tot,samevalue;
	char op[20];
	null=newnode(-1001);
	null->size=null->sum=0;
	root=newnode(-1001);
	root->c[1]=newnode(-1001);
	root->c[1]->father=root;
	root->update();
	scanf("%d%d",&n,&m);
	insertnum(0,n);
	for(;m;--m)
	{
		scanf("%s",op);
		if(op[2]!='X') scanf("%d%d",&pos,&tot);
		if(op[0]=='I') insertnum(pos,tot);
		else if(op[0]=='D')
		{
			select(pos,null);
			select(pos+tot+1,root);
			root->c[1]->c[0]->clear();
			root->c[1]->c[0]=null;
			root->c[1]->update();
			root->update();
		}
		else if(op[0]=='R')
		{
			select(pos,null);
			select(pos+tot+1,root);
			root->c[1]->c[0]->reverse();
			root->c[1]->update();
			root->update();
		}
		else if(op[0]=='G')
		{
			select(pos,null);
			select(pos+tot+1,root);
			printf("%d\n",root->c[1]->c[0]->sum);
		}
		else if(op[2]=='K')
		{
			select(pos,null);
			select(pos+tot+1,root);
			scanf("%d",&samevalue);
			root->c[1]->c[0]->makesame(samevalue);
			root->c[1]->update();
			root->update();
		}
		else printf("%d\n",root->mmax);
	}
	return 0;
}

这里附上块状链表标程:

{
            Program for NOI2005

                           written by Zhou Yuan
                                          2005.07.25
}
{$R-,Q-,S-}
Const
    InFile     = 'sequence.in';
    OutFile    = 'sequence.out';
    SegLen     = 800;
    LimitSave  = 2000;
    maximum    = maxlongint div 2;

Type
    Tcursor    = record
                     p , exec                : longint;
                 end;
    TSegment   = object
                     Len                     : longint;
                     data                    : array[1..SegLen] of longint;
                     sum , samevalue ,
                     InMax , next            : longint;
                     sideMax                 : array[false..true] of longint;
                     reversed , same         : boolean;
                     procedure init;
                     procedure fill;
                     procedure reverseit;
                     procedure Set_Interval(st , ed , c : longint);
                     procedure Set_Same(c : longint);
                     procedure maintain;
                     function Get_Sum(st , ed : longint) : longint;
                 end;
    Tmem       = object
                     open , closed           : longint;
                     free                    : array[1..LimitSave] of longint;
                     procedure init;
                     function _new : longint;
                     procedure _dispose(num : longint);
                 end;
    Tdata      = array[1..LimitSave] of TSegment;

Var
    mem        : Tmem;
    data       : Tdata;
    root , M   : longint;

procedure TSegment.init;
begin
    next := 0; Len := 0; same := false; reversed := false;
end;

procedure TSegment.fill;
var
    i          : longint;
begin
    if not same then exit;
    for i := 1 to Len do data[i] := samevalue;
    same := false;
end;

procedure TSegment.reverseit;
var
    i , tmp    : longint;
begin
    if not reversed then exit;
    if not same then
      for i := 1 to Len div 2 do
        begin
            tmp := data[i]; data[i] := data[Len - i + 1];
            data[Len - i + 1] := tmp;
        end;
    tmp := sidemax[false]; sidemax[false] := sidemax[true]; sidemax[true] := tmp;
    reversed := false;
end;

procedure TSegment.Set_Interval(st , ed , c : longint);
var
    i          : longint;
begin
    reverseit; fill;
    for i := st to ed do data[i] := c;
    maintain;
end;

procedure TSegment.Set_Same(c : longint);
begin
    same := true; samevalue := c;
    maintain;
end;

procedure TSegment.maintain;
var
    i , subsum : longint;
begin
    if same then
      begin
          if samevalue < 0 then InMax := samevalue else InMax := samevalue * Len;
          sum := samevalue * Len; sideMax[false] := InMax; sideMax[true] := InMax; exit;
      end;
    sum := 0; subsum := 0; InMax := data[1];
    for i := 1 to Len do
      begin
          subsum += data[i]; sum += data[i];
          if subsum > InMax then InMax := subsum;
          if subsum < 0 then subsum := 0;
      end;
    sideMax[false] := data[1]; subsum := 0;
    for i := 1 to Len do
      begin
          subsum += data[i];
          if subsum > sideMax[false] then sideMax[false] := subsum;
      end;
    sideMax[true] := data[Len]; subsum := 0;
    for i := Len downto 1 do
      begin
          subsum += data[i];
          if subsum > sideMax[true] then sideMax[true] := subsum;
      end;
end;

function TSegment.Get_Sum(st , ed : longint) : longint;
var
    tmp , i    : longint;
begin
    if same
      then exit(samevalue * (ed - st + 1))
      else begin
               if reversed then
                 begin tmp := Len - st + 1; st := Len - ed + 1; ed := tmp; end;
               tmp := 0;
               for i := st to ed do tmp += data[i];
               exit(tmp);
           end;
end;

procedure Tmem.init;
var
    i          : longint;
begin
    open := 1; closed := 0;
    for i := 1 to LimitSave do free[i] := i;
end;

function Tmem._new : longint;
begin
    if open = closed then
      begin assign(OUTPUT , ''); ReWrite(OUTPUT); writeln('ERROR'); Halt; end;
    _new := free[open]; open += 1; if open > LimitSave then open := 1;
end;

procedure Tmem._dispose(num : longint);
begin
    closed += 1; if closed > LimitSave then closed := 1;
    free[closed] := num;
end;

procedure init;
var
    i , p , N  : longint;
begin
    readln(N , M);
    mem.init; root := mem._new; data[root].Len := SegLen; p := root;
    for i := 1 to N do
      begin
          if data[p].Len = SegLen
            then begin
                     data[p].next := mem._new; data[p].maintain;
                     p := data[p].next; data[p].init;
                 end;
          data[p].Len += 1; read(data[p].data[data[p].Len]);
      end;
    readln;
    data[p].maintain;
end;

procedure Find(posi : longint; var p : Tcursor);
begin
    posi += SegLen; p.p := root;
    while posi > data[p.p].Len do
      begin
          posi -= data[p.p].Len;
          p.p := data[p.p].next;
      end;
    p.exec := posi;
end;

procedure Split(posi : longint; var p : longint);
var
    cursor     : Tcursor;
    i , tmp    : longint;
begin
    Find(posi , cursor);
    if cursor.exec <> data[cursor.p].Len
      then begin
               p := mem._new; data[p].init;
               data[p].next := data[cursor.p].next;
               data[cursor.p].next := p;
               data[cursor.p].reverseit;
               data[cursor.p].fill;
               for i := cursor.exec + 1 to data[cursor.p].Len do
                 begin
                     data[p].Len += 1;
                     data[p].data[data[p].Len] := data[cursor.p].data[i];
                 end;
               data[cursor.p].Len := cursor.exec; p := cursor.p;
               data[p].maintain; data[data[p].next].maintain;
           end
      else begin p := cursor.p; data[p].reverseit; data[p].fill; end;
end;

procedure Zip;
var
    i , j      : longint;
    last       : boolean;
begin
    i := root; last := false;
    while data[i].next <> 0 do
      if data[i].Len + data[data[i].next].Len <= SegLen
        then begin
                 if not last then begin data[i].fill; data[i].reverseit; end;
                 data[data[i].next].fill; data[data[i].next].reverseit;
                 for j := 1 to data[data[i].next].Len do
                   begin
                       data[i].Len += 1;
                       data[i].data[data[i].Len] := data[data[i].next].data[j];
                   end;
                 j := data[i].next; data[i].next := data[j].next;
                 mem._dispose(j); last := true;
             end
        else begin
                 if last then data[i].maintain;
                 i := data[i].next; last := false;
             end;
    if last then data[i].maintain;
end;

procedure Insert_Block;
var
    i , posi ,
    tot , p , tp
               : longint;
begin
    read(posi , tot); Split(posi , p);
    while tot > 0 do
      begin
          if data[p].Len = SegLen then
            begin
                data[p].maintain;
                tp := mem._new; data[tp].init; data[tp].next := data[p].next;
                data[p].next := tp; p := tp;
            end;
          data[p].Len += 1; read(data[p].data[data[p].Len]);
          tot -= 1;
      end;
    data[p].maintain;
    readln;
    Zip;
end;

procedure Delete_Block;
var
    i , posi , tot ,
    st , ed , t: longint;
begin
    readln(posi , tot); posi -= 1;
    Split(posi , st);
    Split(posi + tot , ed);
    repeat
      t := data[st].next; data[st].next := data[t].next;
      mem._dispose(t);
    until t = ed;
    Zip;
end;

procedure Make_Same;
var
    posi , c ,
    tot , t    : longint;
    st , ed    : Tcursor;
begin
    readln(posi , tot , c); posi -= 1;
    Find(posi , st); Find(posi + tot , ed);
    if st.p = ed.p
      then data[st.p].Set_Interval(st.exec + 1 , ed.exec , c)
      else begin
               data[st.p].Set_Interval(st.exec + 1 , data[st.p].Len , c);
               data[ed.p].Set_Interval(1 , ed.exec , c);
               t := st.p;
               while data[t].next <> ed.p do
                 begin
                     t := data[t].next;
                     data[t].Set_Same(c);
                 end;
           end;
end;

procedure Reverse_Block;
var
    sum , i ,
    posi , tot ,
    st , ed , t: longint;
    list       : array[1..LimitSave] of longint;
begin
    readln(posi , tot); posi -= 1;
    Split(posi , st); Split(posi + tot , ed);
    t := data[st].next; data[st].next := ed;
    sum := 1; list[1] := t;
    while t <> ed do
      begin t := data[t].next; sum += 1; list[sum] := t; end;
    for i := 1 to sum do data[list[i]].reversed := not data[list[i]].reversed;
    data[list[1]].next := data[ed].next;
    for i := sum downto 2 do data[list[i]].next := list[i - 1];
    Zip;
end;

procedure Get_Sum;
var
    posi , tot ,
    i , answer : longint;
    st , ed    : Tcursor;
begin
    readln(posi , tot); posi -= 1;
    Find(posi , st); Find(posi + tot , ed);
    if st.p = ed.p
      then answer := data[st.p].Get_Sum(st.exec + 1 , ed.exec)
      else begin
               answer := data[st.p].Get_Sum(st.exec + 1 , data[st.p].Len)
                       + data[ed.p].Get_Sum(1 , ed.exec);
               i := st.p;
               while data[i].next <> ed.p do
                 begin
                     i := data[i].next;
                     answer += data[i].sum;
                 end;
           end;
    writeln(answer);
end;

function fmax(a , b : longint) : longint;
begin
    if a > b then exit(a) else exit(b);
end;

procedure Get_MaxSum;
var
    answer , i ,
    last       : longint;
begin
    readln; answer := -maximum; last := -maximum;
    i := data[root].next;
    while i <> 0 do
      begin
          answer := fmax(answer , data[i].InMax);
          answer := fmax(answer , last + data[i].sideMax[data[i].reversed]);
          last := fmax(last + data[i].sum , data[i].sidemax[not data[i].reversed]);
          i := data[i].next;
      end;
    writeln(answer);
end;

procedure print_all;
var
    i , j      : longint;
begin
    i := root;
    while data[i].next <> 0 do
      begin
          i := data[i].next;
          if data[i].reversed
            then for j := data[i].Len downto 1 do
                   write(data[i].data[j] , ' ')
            else for j := 1 to data[i].Len do
                   write(data[i].data[j] , ' ');
      end;
    writeln;
end;

procedure workout;
var
    i          : longint;
    signal     : string[20];
    ch         : char;
begin
    for i := 1 to M do
      begin
          signal := ''; read(ch);
          while ch <> ' ' do
            begin signal += ch; if eoln then ch := ' ' else read(ch); end;
          if signal = 'INSERT' then Insert_Block;
          if signal = 'DELETE' then Delete_Block;
          if signal = 'MAKE-SAME' then make_Same;
          if signal = 'REVERSE' then Reverse_Block;
          if signal = 'GET-SUM' then Get_Sum;
          if signal = 'MAX-SUM' then Get_MaxSum;
//          print_all;
      end;
end;

Begin
    assign(INPUT , InFile); ReSet(INPUT);
    assign(OUTPUT , OutFile); ReWrite(OUTPUT);
      init;
      workout;
    Close(OUTPUT);
    Close(INPUT);
End.
Posted in NOI. Tags: , , , . 5 Comments »

USACO OPEN09 Gold Summary

Ski Lessons

动态规划

f[i][j]表示时刻i时滑雪能力为j的前提下,前i个时间单位里最多能滑多少次雪。我们采用向后递推法转移,有三种选择:(1)喝汽水;(2)参加时刻i开始的课程,能力值改变;(3)在能滑的雪坡中选择一个滑。

现在障碍在于雪坡数太多,每次都尝试所有能滑的雪坡转移代价太高。实际上我们并不需要尝试所有能滑的雪坡,因为滑雪用的时间越短越好,而滑任何雪坡效果都是一样的,能滑的雪坡之间没有优劣之分,所以要选择能滑的雪坡中用时最短的那个。通过预处理,可以在常数时间内获取这个信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
ID: lipeiqi1
LANG: C++
PROB: ski
*/
#include <iostream>
 
using namespace std;
 
struct tlesson
{
    int start,length,ability;
} lesson[10001];
int prev[101][101],mintime[101],f[10001][101];
 
bool operator<(const tlesson &a,const tlesson &b)
{
    return a.start<b.start;
}
 
void update(int &u,int v)
{
    if(u<v) u=v;
}
 
int main()
{
    freopen("ski.in","r",stdin);
    freopen("ski.out","w",stdout);
    int t,s,n,require,length;
    scanf("%d%d%d",&t,&s,&n);
    for(int i=0;i<s;++i) scanf("%d%d%d",&lesson[i].start,&lesson[i].length,&lesson[i].ability);
    sort(lesson,lesson+s);
    for(int i=1;i<101;++i) mintime[i]=INT_MAX;
    for(int i=0;i<n;++i)
    {
        scanf("%d%d",&require,&length);
        for(int j=require;j<101;++j) mintime[j]=min(mintime[j],length);
    }
    memset(f,-1,sizeof(f));
    f[0][1]=0;
    int cur=0,ans=-1;
    for(int i=0;i<=t;++i)
    {
        while(cur<s&&lesson[cur].start<i) ++cur;
        for(int j=1;j<101;++j)
            if(f[i][j]>-1)
            {
                update(ans,f[i][j]);
                if(i<t) update(f[i+1][j],f[i][j]);
                if(cur<s&&lesson[cur].start==i&&i+lesson[cur].length<=t) update(f[i+lesson[cur].length][lesson[cur].ability],f[i][j]);
                if(mintime[j]<INT_MAX&&i+mintime[j]<=t) update(f[i+mintime[j]][j],f[i][j]+1);
            }
    }
    printf("%d\n",ans);
    return 0;
}
Posted in Gold Division, USACO. Tags: , , . No Comments »

POI 2000 Skiers

贪心策略、深度优先搜索

网络流模型理论上可以解决此题,把除1和n以外的每个结点都拆成两个结点,中间用一条容量为1的边连接,结点1为源,结点n为汇求最大流。无奈本题数据规模较大,最多有5000个结点,而且边数未知。

注意到题目给出的图具有特殊的性质:边是按从东到西的顺序给出的,而且所有边不相交。这样我们可以采取如下贪心策略:用深度优先搜索从结点1出发找一条尽可能靠东(或尽可能靠西)的路径,让一个运动员沿此路径滑雪,把此路径上的除起点和终点外的所有结点从图中删去,然后重复此过程。这个策略显然正确,由于所有边都不相交,每次找最靠东(或最靠西)的路径可以给后面要找的路径腾出最大的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
 
using namespace std;
 
struct edge
{
	int v;
	edge *next;
	edge(int v,edge *next):v(v),next(next) {}
} *e[5005];
int n;
bool u[5005];
 
bool dfs(int v)
{
	if(v==n) return true;
	u[v]=true;
	for(edge *t=e[v];t;t=t->next)
		if(!u[t->v]&&dfs(t->v))
			return true;
	return false;
}
 
int main()
{
	freopen("nar.in","r",stdin);
	freopen("nar.out","w",stdout);
	scanf("%d",&n);
	int t,v,ans=0;
	for(int i=1;i<n;++i)
		for(scanf("%d",&t);t;--t)
		{
			scanf("%d",&v);
			e[i]=new edge(v,e[i]);
		}
	for(edge *p=e[1];p;p=p->next)
		if(!u[p->v]&&dfs(p->v))
			++ans;
	printf("%d\n",ans);
	return 0;
}
Posted in POI. Tags: , . No Comments »

POI 1999 Water

优先队列

注意到边界上的格子一定不能存水,否则会流到外面去。现在考虑与边界相邻的格子。注满水后,对于某个与边界相邻的格子来水,它的积水状态只与它自己的恶高度和相邻边界格子的最低高度有关。这样,我们可以取边界格子中高度最低的格子,然后来得到与之相邻的内部格子的水位。假设边界格子中高度最低的为格子(x,y),那么对于(x,y)的任意一个相邻非边界格子(x’,y’)来说,只要h(x,y)>h(x’,y’)” />,格子(x’,y’)就会有高度为<img src=的积水,这时我们可以把h(x,y)-h(x',y')累加到答案中去,然后把h(x',y')赋为h(x,y)并把(x’,y’)看作边界格子。如果h(x,y)<=h(x',y'),我们可以直接把(x’,y’)看作边界格子。当处理完所有(x,y)的相邻非边界格子后,把(x,y)从边界格子中删去。重复这个过程,直至边界格子集合为空为止。显然,用一个小根堆来维护边界格子集合是在适合不过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
 
using namespace std;
 
const int dx[]={-1,1,0,0},dy[]={0,0,-1,1};
bool u[111][111];
int ans,heapsize,h[111][111];
struct node
{
	int x,y,v;
} heap[11111];
 
void insert(int x,int y,int v)
{
	node key={x,y,v};
	int p=++heapsize;
	while(p>1&&heap[p>>1].v>v)
	{
		heap[p]=heap[p>>1];
		p>>=1;
	}
	heap[p]=key;
}
 
void deleteMin()
{
	int p=1;
	while((p<<1)<heapsize)
	{
		int i=p<<1;
		if(i+1<heapsize&&heap[i+1].v<heap[i].v) ++i;
		if(heap[i].v<heap[heapsize].v)
		{
			heap[p]=heap[i];
			p=i;
		}
		else break;
	}
	heap[p]=heap[heapsize--];
}
 
int main()
{
	freopen("wod.in","r",stdin);
	freopen("wod.out","w",stdout);
	int r,c;
	scanf("%d%d",&r,&c);
	for(int i=0;i<r;++i)
		for(int j=0;j<c;++j)
			scanf("%d",&h[i][j]);
	for(int i=0;i<c;++i)
	{
		u[0][i]=u[r-1][i]=true;
		insert(0,i,h[0][i]);
		insert(r-1,i,h[r-1][i]);
	}
	for(int i=1;i+1<r;++i)
	{
		u[i][0]=u[i][c-1]=true;
		insert(i,0,h[i][0]);
		insert(i,c-1,h[i][c-1]);
	}
	while(heapsize)
	{
		int x=heap[1].x,y=heap[1].y;
		deleteMin();
		for(int i=0;i<4;++i)
		{
			int tx=x+dx[i],ty=y+dy[i];
			if(tx>-1&&tx<r&&ty>-1&&ty<c&&!u[tx][ty])
			{
				u[tx][ty]=true;
				if(h[tx][ty]<h[x][y])
				{
					ans+=h[x][y]-h[tx][ty];
					h[tx][ty]=h[x][y];
				}
				insert(tx,ty,h[tx][ty]);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
Posted in POI. Tags: , . No Comments »

POI 2003 Monkeys

并查集

这是一道很棒的并查集类问题,具有较高的思维难度。注意到题目中所描述的猴子的连接方式,有可能猴子a抓住猴子b的尾巴,也有可能b抓住a的尾巴,也有可能a和b互相抓住对方的尾巴。其实我们只关心两个猴子是否相连,以上三种情况都是猴子a和b相连。现在题目要求的就是每只猴子第一次直接或间接脱离猴子1的时间。看起来好像和并查集的操作是相反的,那我们就反过来求解。首先求出时刻m所有放手行为都执行后猴子们的连接情况,然后从时刻m往前反过来执行“抓尾巴”行为,这样问题转化为求每个猴子第一次直接或间接与猴子1相连的时刻。这正是并查集所支持的操作,只是每次将猴子1所在集合与其它集合合并时,需要把那个集合的所有猴子的答案都设置为这个时刻。我们可以另外对每个集合维护一个链表,包含这个集合的所有猴子,合并时将链表合并。这样总的时间复杂度不变,问题成功解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
 
using namespace std;
 
struct node
{
	int v;
	node *next;
	node(int v,node *next):v(v),next(next) {}
} *first[200002],*last[200002];
int d[400004][2],holdOrig[200002][2],holdCur[200002][2],ans[200002],f[200002];
 
int getRoot(int v)
{
	if(f[v]==v) return v;
	else return f[v]=getRoot(f[v]);
}
 
void unite(int a,int b)
{
	int x=getRoot(a),y=getRoot(b);
	if(x!=y)
	{
		f[x]=y;
		last[y]->next=first[x];
		last[y]=last[x];
	}
}
 
void uniteAndUpdateAns(int a,int b,int moment)
{
	int x=getRoot(a),y=getRoot(b);
	if(x!=y)
	{
		if(x==getRoot(1))
			for(node *t=first[y];t;t=t->next)
				ans[t->v]=moment;
		else if(y==getRoot(1))
			for(node *t=first[x];t;t=t->next)
				ans[t->v]=moment;
		f[x]=y;
		last[y]->next=first[x];
		last[y]=last[x];
	}
}
 
int main()
{
	int n,m;
	memset(ans,-1,sizeof(ans));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d",&holdOrig[i][0],&holdOrig[i][1]);
		holdCur[i][0]=holdOrig[i][0];
		holdCur[i][1]=holdOrig[i][1];
	}
	for(int i=0;i<m;++i)
	{
		scanf("%d%d",&d[i][0],&d[i][1]);
		--d[i][1];
		holdCur[d[i][0]][d[i][1]]=-1;
	}
	for(int i=1;i<=n;++i)
	{
		f[i]=i;
		first[i]=last[i]=new node(i,NULL);
	}
	for(int i=1;i<=n;++i)
	{
		if(holdCur[i][0]>-1) unite(i,holdCur[i][0]);
		if(holdCur[i][1]>-1) unite(i,holdCur[i][1]);
	}
	for(int i=m-1;i>-1;--i)
		if(holdOrig[d[i][0]][d[i][1]]>-1)
			uniteAndUpdateAns(d[i][0],holdOrig[d[i][0]][d[i][1]],i);
	for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}
Posted in POI. Tags: , . No Comments »

POI 1998 Word Equations

并查集

本题中不同的变量可以表示不同长度的01串,这给解题带来了麻烦。我们可以把所有长度为p的变量拆成p个长度为1的变量,以简化问题。这样,所有拆分后得到的变量都只能表示一个0或1。假设等号两边的长度都为L,如果把0和1本身也看成特殊的变量的话,等式告诉我们的就是L个相等关系,即某两个变量相等。用并查集维护这些相等关系:初始时每个变量自成一个集合,每遇到一个相等关系,把相等的变量所在集合合并。如果某一时刻0和1所在的集合被合并,则说明出现矛盾,输出0;否则,没有矛盾出现,假设最终除0和1所在集合外共有r个集合,则答案为2^{r}

注意测试数据中存在展开后等式两边长度不等的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
 
using namespace std;
 
char s1[10011],s2[10011];
int total,len[33],s[10011],f[10011],size[10011],ans[11111111];
 
int getRoot(int v)
{
	if(f[v]==v) return v;
	else return f[v]=getRoot(f[v]);
}
 
void unite(int a,int b)
{
	int x=getRoot(a),y=getRoot(b);
	if(x!=y)
	{
		if(size[x]<size[y])
		{
			size[x]+=size[y];
			f[y]=x;
		}
		else
		{
			size[y]+=size[x];
			f[x]=y;
		}
		--total;
	}
}
 
int main()
{
	freopen("row.in","r",stdin);
	freopen("row.out","w",stdout);
	int T;
	for(scanf("%d",&T);T;--T)
	{
		int num,l1,l2;
		scanf("%d",&num);
		for(int i=0;i<num;++i) scanf("%d",len+i);
		scanf("%d\n%s\n%d\n%s",&l1,s1,&l2,s2);
		for(int i=1;i<num;++i) len[i]+=len[i-1];
		for(int i=num;i>0;--i) len[i]=len[i-1]+2;
		len[0]=2;
		int top=0;
		for(int i=0;i<l1;++i)
			if(s1[i]>='a'&&s1[i]<='z')
				for(int j=len[s1[i]-'a'];j<len[s1[i]-'a'+1];++j)
					s[top++]=j;
			else s[top++]=s1[i]-'0';
		for(int i=0;i<10005;++i) f[i]=i,size[i]=1;
		int firstTop=top;
		top=0;
		total=len[num]-2;
		for(int i=0;i<l2;++i)
		{
			if(s2[i]>='a'&&s2[i]<='z')
				for(int j=len[s2[i]-'a'];j<len[s2[i]-'a'+1];++j)
					unite(s[top++],j);
			else unite(s[top++],s2[i]-'0');
		}
		if(getRoot(0)==getRoot(1)||top!=firstTop) puts("0");
		else
		{
			memset(ans,0,sizeof(ans));
			int m=1;
			ans[0]=1;
			for(;total;--total)
			{
				int x=0;
				for(int i=0;i<m;++i)
				{
					ans[i]=(ans[i]<<1)+x;
					if(ans[i]>9) x=1,ans[i]-=10;
					else x=0;
				}
				if(x) ans[m++]=1;
			}
			for(int i=m-1;i>=0;--i) putchar('0'+ans[i]);
			putchar('\n');
		}
	}
	return 0;
}
Posted in POI. Tags: , . No Comments »

POI 1999 Three-coloring of Binary Trees

树型动态规划

首先根据给出的先序遍历序列建树,然后用两遍树型动态规划求出最大和最小值。假设绿色用0表示,1、2表示另外两种颜色。fmax(v,c)表示以v为根的子树中根结点v的颜色为c时的绿色结点数最大值。当v为叶结点时,fmax(v,0)=1fmax(v,1)=fmax(v,2)=0;当v为非叶结点时,我们要给v的孩子结点分配不同于c的颜色,并在所有方案中去最大值,还有不要忘记当c为0时把根结点v自己加进去。求最小值与之类似,只要把所有的max都变为min即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
 
using namespace std;
 
int m,p,num[10011],cleft[10011],cright[10011],f[10011][3];
char s[10011];
 
int max(int a,int b,int c)
{
	return max(max(a,b),c);
}
 
int min(int a,int b,int c)
{
	return min(min(a,b),c);
}
 
void buildTree()
{
	num[m]=s[p++]-'0';
	if(num[m]==1)
	{
		cleft[m]=m+1;
		cright[m]=-1;
		++m;
		buildTree();
		return;
	}
	if(num[m]==2)
	{
		int t=m;
		cleft[t]=m+1;
		++m;
		buildTree();
		cright[t]=m;
		buildTree();
		return;
	}
	if(num[m]==0) cleft[m]=cright[m++]=-1;
}
 
int getMax(int v,int c)
{
	if(f[v][c]>-1) return f[v][c];
	int r=(c==0);
	if(num[v]==1) r+=max(getMax(cleft[v],(c+1)%3),getMax(cleft[v],(c+2)%3));
	else if(num[v]==2) r+=max(getMax(cleft[v],(c+1)%3)+getMax(cright[v],(c+2)%3),getMax(cleft[v],(c+2)%3)+getMax(cright[v],(c+1)%3));
	return f[v][c]=r;
}
 
int getMin(int v,int c)
{
	if(f[v][c]>-1) return f[v][c];
	int r=(c==0);
	if(num[v]==1) r+=min(getMin(cleft[v],(c+1)%3),getMin(cleft[v],(c+2)%3));
	else if(num[v]==2) r+=min(getMin(cleft[v],(c+1)%3)+getMin(cright[v],(c+2)%3),getMin(cleft[v],(c+2)%3)+getMin(cright[v],(c+1)%3));
	return f[v][c]=r;
}
 
int main()
{
	freopen("trot.in","r",stdin);
	freopen("trot.out","w",stdout);
	gets(s);
	buildTree();
	memset(f,-1,sizeof(f));
	printf("%d ",max(getMax(0,0),getMax(0,1),getMax(0,2)));
	memset(f,-1,sizeof(f));
	printf("%d\n",min(getMin(0,0),getMin(0,1),getMin(0,2)));
	return 0;
}
Posted in POI. Tags: , , . No Comments »

POI 1998 Frogman, SPOJ 181 Scuba diver Summary

动态规划

本题中的物品涉及两个价值,属于背包问题的简单变形,氧气和氮气各占一维即可。和普通的一维背包问题一样,这里可以通过调整状态转移的顺序来实现空间降维。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cstdio>
#include <cstring>
 
int f[80][80];
 
int main()
{
    int m,n,a,b,w,num,T;
    for(scanf("%d",&T);T;--T)
    {
        memset(f,-1,sizeof(f));
        f[0][0]=0;
        scanf("%d%d%d",&m,&n,&num);
        for(int k=0;k<num;++k)
        {
            scanf("%d%d%d",&a,&b,&w);
            for(int i=79;i>=a;--i)
                for(int j=79;j>=b;--j)
                    if(f[i-a][j-b]>-1&&(f[i][j]==-1||f[i][j]>f[i-a][j-b]+w))
                        f[i][j]=f[i-a][j-b]+w;
        }
        int ans=-1;
        for(int i=m;i<80;++i)
            for(int j=n;j<80;++j)
                if(f[i][j]>-1&&(ans==-1||ans>f[i][j]))
                    ans=f[i][j];
        printf("%d\n",ans);
    }
    return 0;
}
Posted in SPOJ. Tags: , , . No Comments »

TopCoder SRM 429 Summary

SubrectanglesOfTable
简单统计题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cmath>
#include <ctime>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include <string>
#include <vector>
 
using namespace std;
 
class SubrectanglesOfTable
{
public:
	vector<long long> getQuantity(vector <string> table)
	{
		vector <long long> r(26);
		int m=table.size();
		for(int i=0;i<m;++i)
		{
			table[i]+=table[i];
			table.push_back(table[i]);
		}
		m<<=1;
		int n=table[0].size();
		for(long long i=0;i<m;++i)
			for(long long j=0;j<n;++j)
				r[table[i][j]-'A']+=(i+1)*(j+1)*(m-i)*(n-j);
		return r;
	}
};

IngredientProportions
构造
随便给某个原料(比如原料0)设定一个质量值,由于题目保证有唯一解,通过给出的比例关系一定可以求出其它所有原料的质量,最后将所有原料的质量统一放大或缩小到最简整数比即可得到正确解。为避免分数运算,我们可以给原料0分配一个恰当的整数质量使得以后的运算中不出现分数。先运用类似Floyd的方法求出原料0与其它所有原料的直接比例,求完后假设原料0与任意原料k的质量比为A[k]:B[k],那么为使求得的原料k的质量为整数,原料0的质量必须为A[k]的倍数,因此我们应该给原料0分配A[1..N-1]所有数的最小公倍数这么大的质量。这样,我们得到了所有原料的整数质量,最后求出它们的最大公约数而后化为最简比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <cmath>
#include <ctime>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include <string>
#include <vector>
 
using namespace std;
 
int p[11][11][2];
long long f[11];
 
long long gcd(long long a,long long b)
{
	if(b==0) return a;
	else return gcd(b,a%b);
}
 
class IngredientProportions
{
public:
	vector <int> getMasses(vector <string> d)
	{
		memset(p,0,sizeof(p));
		int n=d.size()+1;
		for(int i=0;i<d.size();++i)
		{
			int a=d[i][1]-'0',b=d[i][8]-'0',r=d[i][13]-'0',s=d[i][15]-'0';
			p[a][b][0]=r;
			p[a][b][1]=s;
			p[b][a][0]=s;
			p[b][a][1]=r;
		}
		for(int k=0;k<n;++k)
			for(int i=0;i<n;++i)
				if(p[i][k][0])
					for(int j=0;j<n;++j)
						if(p[k][j][0]&&!(p[i][j][0]))
						{
							p[i][j][0]=p[i][k][0]*p[k][j][0];
							p[i][j][1]=p[i][k][1]*p[k][j][1];
						}
		f[0]=1;
		for(int i=1;i<n;++i) f[0]=f[0]/gcd(f[0],p[0][i][0])*p[0][i][0];
		for(int i=1;i<n;++i) f[i]=f[0]/p[0][i][0]*p[0][i][1];
		long long g=f[0];
		for(int i=1;i<n;++i) g=gcd(g,f[i]);
		vector <int> r;
		for(int i=0;i<n;++i) r.push_back(f[i]/g);
		return r;
	}
};

SpecificPolyominoCovering
构造
本题的难点在于如何快速判断一个棋盘能否用给定的两种棋子填满。
注意到所有的AB嵌套棋子
ABBA
AAAA
都可以用4个B型棋子
BBBB
BBBB
替换,所以在判定时我们可以禁止AB嵌套棋子,而不失正确性。这样我们可以从上到下,从左到右扫描棋盘,每次遇到空缺先尝试B棋子再尝试A棋子,如果都不成功则判定失败,否则继续判定。(详细的正确性证明请参考官方题解)
注意到我们的判定过程实际上构造了一组解,但题目要求我们返回字典序最小的解。我们可以多次利用上述判定过程,仍然从上到下,从左到右扫描棋盘,每次遇到空缺先尝试A棋子,如果可以成功放进去A棋子并且调用上述过程判定剩下的棋盘可以填满棋子,那么就把A棋子真正放到棋盘上,否则就放B棋子,这样可以正确得出字典序最小的解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <cmath>
#include <ctime>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include <string>
#include <vector>
 
using namespace std;
 
int r,c;
bool u[55][55];
 
bool able(const vector <string> &g)
{
	memset(u,false,sizeof(u));
	for(int i=0;i<r;++i)
		for(int j=0;j<c;++j)
			if(g[i][j]=='X'&&!u[i][j])
			{
				if(j+1<c&&g[i][j+1]=='X') u[i][j+1]=true;
				else if(i+1<r&&j+3<c&&g[i][j+3]=='X'&&g[i+1][j]=='X'&&g[i+1][j+1]=='X'&&g[i+1][j+2]=='X'&&g[i+1][j+3]=='X') u[i][j+3]=u[i+1][j]=u[i+1][j+1]=u[i+1][j+2]=u[i+1][j+3]=true;
				else return false;
			}
	return true;
}
 
class SpecificPolyominoCovering
{
public:
	vector <string> findCovering(vector <string> g)
	{
		r=g.size();
		c=g[0].size();
		if(!able(g)) return vector <string>();
		for(int i=0;i<r;++i)
			for(int j=0;j<c;++j)
				if(g[i][j]=='X')
				{
					if(i+1<r&&j+3<c&&g[i][j+3]=='X'&&g[i+1][j]=='X'&&g[i+1][j+1]=='X'&&g[i+1][j+2]=='X'&&g[i+1][j+3]=='X')
					{
						g[i][j]=g[i][j+3]=g[i+1][j]=g[i+1][j+1]=g[i+1][j+2]=g[i+1][j+3]='A';
						if(able(g)) continue;
						else g[i][j]=g[i][j+3]=g[i+1][j]=g[i+1][j+1]=g[i+1][j+2]=g[i+1][j+3]='X';
					}
					g[i][j]=g[i][j+1]='B';
				}
		return g;
	}
};
Posted in SRM, TopCoder. Tags: , , . No Comments »

TopCoder SRM 431 Summary

LaserShooting
简单概率题
显然本题中target并不阻挡射线,所以各个targe之间互不影响,那么射中个数的数学期望值就是射中各个targe的概率和。值得注意的是C++中求反正切arctan时最好用atan2(y,x)函数,因为它不仅能处理斜率不存在的情况还能正确处理角度符号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <cmath>
#include <ctime>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include <string>
#include <vector>
 
using namespace std;
 
const double PI=3.1415926535;
 
class LaserShooting
{
public:
	double numberOfHits(vector <int> x, vector <int> y1, vector <int> y2)
	{
		int n=x.size();
		for(int i=0;i<n;++i)
			if(y1[i]>y2[i])
				swap(y1[i],y2[i]);
		double ans=0;
		for(int i=0;i<n;++i) ans+=(atan2(y2[i],x[i])-atan2(y1[i],x[i]))/PI;
		return ans;
	}
};

MegaCoolNumbers

动态规划
首先挖掘一下cool number of power A这个定义的本质:一个N位数如果是cool number of power A(A9时直接返回0,下面我们考虑A<=9的情况。采用动态规划,f[n][i][last][diff]表示所有n位数中最小power值为i且最后一个数字为last且最后一段的公差为diff的mega cool numbers个数(其中0<=diff<=8,用diff=9表示最后一组仅包含last这一个数字的情况,表示此时公差不确定),按照题意容易想出状态转移方法,这里不赘述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cmath>
#include <ctime>
#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include <string>
#include <vector>
 
using namespace std;
 
const int MOD=1000000007;
int f[1001][10][10][10];
 
class MegaCoolNumbers
{
public:
	int count(int n,int A)
	{
		if(A>9) return 0;
		memset(f,0,sizeof(f));
		for(int i=1;i<10;++i) f[1][1][i][9]=1;
		for(int i=2;i<=n;++i)
			for(int j=1;j<10;++j)
				for(int last=1;last<10;++last)
				{
					for(int diff=0;diff<last;++diff) f[i][j][last][diff]=(f[i-1][j][last-diff][diff]+f[i-1][j][last-diff][9])%MOD;
					for(int prev=1;prev<=last;++prev)
						for(int diff=0;diff<9;++diff)
							if(diff!=last-prev)
								f[i][j][last][9]=(f[i][j][last][9]+f[i-1][j-1][prev][diff])%MOD;
				}
		int ans=0;
		for(int i=1;i<10;++i)
			for(int j=0;j<10;++j)
				ans=(ans+f[n][A][i][j])%MOD;
		return ans;
	}
};
Posted in SRM, TopCoder. Tags: , , . No Comments »