NOI2008 Day 1 解题报告
第一次做NOI的套题,觉得还是有很多可以进步的地方。
不多说了,直接上题,代码写的丑,求各位神犇不要D我
假面舞会
【问题描述】
一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第i 类面具的人才能看到戴第i+1 类面具的人的编号,戴第k 类面具的人能看到戴第1 类面具的人的编号。参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k≥3,所以你必须将这条信息也考虑进去。
【输入格式】
输入文件 party.in 第一行包含两个整数n, m,用一个空格分隔,n 表示主办方总共准备了多少个面具,m 表示栋栋收集了多少条信息。接下来m 行,每行为两个用空格分开的整数a, b,表示戴第a 号面具的人看到了第b 号面具的编号。相同的数对a, b 在输入文件中可能出现多次。
【输出格式】
输出文件 party.out 包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少3 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1。
【输入样例一】
6 5
1 2
2 3
3 4
4 1
3 5
【输出样例一】
4 4
【输入样例二】
3 3
1 2
2 1
2 3
【输出样例二】
-1 -1
【数据规模和约定】
50%的数据,满足n ≤ 300, m ≤ 1000;
100%的数据,满足n ≤ 100000, m ≤ 1000000。
思路:
第一个问题,什么是限制这个环长度的因素。当我们按照所给关系进行DFS遍历的时候,我们会发现有可能遍历到曾经访问过的点,那么这个时候,为了保证解的合法性,这个所遍历到的环的长度一定是原环长度的(也就是分K类的K值)的倍数。如果遍历到很多环呢,显然,原环的长度一定是所遍历到环长度的公约数。到此,算法思路就非常明显了。DFS找环,求所有环长的最大公约数T。
这个题的情况比较多。首先,所有点不一定在一棵树上,那么我们需要遍历整个森林。如果T小于3,那么不存在合法解,如果T大于3,然后找T最小的大于3的约数(包括T本身)。还有一种特殊情况,当T=0的时候,原图不存在环,那么我们可以随意构造其长度,但是注意,这个长度的最大值一定要大于3,否则也是无解的情况。
题目相对较为简单,不过情况比较多,处理时必须得严谨
代码:
program party;
type
point=^node;
node=record
data,num:longint;
next:point;
end;
var
i,j:longint;
pp:array[1..100010]of boolean;
n,m:longint;
nu:array[1..100010]of longint;
way,last:array[1..100010]of point;
x,y:longint;
min,max,leng:longint;
t:longint;
function gcd(a,b:longint):longint;
begin
if b=0 then exit(a)
else exit(gcd(b,a mod b));
end;
procedure build(x,y,z:longint);
begin
if way[x]=nil then begin
new(way[x]);
way[x]^.data:=y;
way[x]^.next:=nil;
way[x]^.num:=z;
last[x]:=way[x];
exit;
end;
new(last[x]^.next);
last[x]^.next^.data:=y;
last[x]^.next^.next:=nil;
last[x]^.next^.num:=z;
last[x]:=last[x]^.next;
end;
procedure dfs(v:longint);
var
p:point;
begin
pp[v]:=true;
p:=way[v];
if max<nu[v] then max:=nu[v];
if min>nu[v] then min:=nu[v];
while p<>nil do
begin
if not pp[p^.data] then begin
nu[p^.data]:=nu[v]+p^.num;
dfs(p^.data);
end
else begin
if t=0 then t:=abs(nu[v]+p^.num-nu[p^.data])
else t:=gcd(t,abs(nu[v]+p^.num-nu[p^.data]));
end;
p:=p^.next;
end;
end;
begin
assign(input,'party.in');
assign(output,'party.out');
reset(input);
rewrite(output);
readln(n,m);
for i:=1 to n do
way[i]:=nil;
for i:=1 to m do
begin
readln(x,y);
build(x,y,1);
build(y,x,-1);
end;
fillchar(pp,sizeof(pp),false);
leng:=0;
for i:=1 to n do
if not pp[i] then begin
min:=maxlongint;
max:=0;
dfs(i);
leng:=leng+max-min+1;
end;
if t=0 then begin
if leng<3 then writeln(-1,' ',-1)
else writeln(leng,' ',3);
end
else begin
if t<3 then writeln(-1,' ',-1)
else begin
for i:=3 to t do
if t mod i=0 then break;
writeln(t,' ',i);
end;
end;
close(input);
close(output);
end.
设计路线
【问题描述】
Z国坐落于遥远而又神奇的东方半岛上,在小Z的统治时代公路成为这里主要的交通手段。Z国共有n座城市,一些城市之间由双向的公路所连接。非常神奇的是Z国的每个城市所处的经度都不相同,并且最多只和一个位于它东边的城市直接通过公路相连。Z国的首都是Z国政治经济文化旅游的中心,每天都有成千上万的人从Z国的其他城市涌向首都。为了使Z国的交通更加便利顺畅,小Z决定在Z国的公路系统中确定若干条规划路线,将其中的公路全部改建为铁路。 我们定义每条规划路线为一个长度大于1的城市序列,每个城市在该序列中最多出现一次,序列中相邻的城市之间由公路直接相连(待改建为铁路)。并且,每个城市最多只能出现在一条规划路线中,也就是说,任意两条规划路线不能有公共部分。当然在一般情况下是不可能将所有的公路修建为铁路的,因此从有些城市出发去往首都依然需要通过乘坐长途汽车,而长途汽车只往返于公路连接的相邻的城市之间,因此从某个城市出发可能需要不断地换乘长途汽车和火车才能到达首都。
我们定义一个城市的“不便利值”为从它出发到首都需要乘坐的长途汽车的次数,而Z国的交通系统的“不便利值”为所有城市的不便利值的最大值,很明显首都的“不便利值”
为0。小Z想知道如何确定规划路线修建铁路使得Z国的交通系统的“不便利值”最小,以及有多少种不同的规划路线的选择方案使得“不便利值”达到最小。当然方案总数可能非常大,小Z只关心这个天文数字mod Q后的值。 注意:规划路线1-2-3和规划路线3-2-1是等价的,即将一条规划路线翻转依然认为是等价的。两个方案不同当且仅当其中一个方案中存在一条规划路线不属于另一个方案。
【输入格式】
第一行包含三个正整数N、M、Q,其中N表示城市个数,M表示公路总数,N个城市从1~N编号,其中编号为1的是首都。Q表示上文提到的设计路线的方法总数的模数。接下来M行,每行两个不同的正数ai、bi (1≤ ai , bi ≤ N)表示有一条公路连接城市ai和城市bi。
输入数据保证一条公路只出现一次。
【输出格式】
应包含两行。第一行为一个整数,表示最小的“不便利值”。
第二行为一个整数,表示使“不便利值”达到最小时不同的设计路线的方法总数mod Q的值。如果某个城市无法到达首都,则输出两行-1。
【输入样例】
5 4 100
1 2
4 5
1 3
4 1
【输出样例】
1
10
【样例说明】
以下样例中是10种设计路线的方法:
(1) 4-5
(2) 1-4-5
(3) 4-5, 1-2
(4) 4-5, 1-3
(5) 4-5, 2-1-3
(6) 2-1-4-5
(7) 3-1-4-5
(8) 1-4
(9) 2-1-4
(10) 3-1-4
【数据规模和约定】
对于20%的数据,满足N,M ≤ 10。 对于50%的数据,满足N,M ≤200。
对于60%的数据,满足N,M ≤5000。
对于100%的数据,满足1 ≤ N,M ≤100000,1 ≤ Q ≤ 120000000。
思路:第一问很好去做,影响不便利值只有可能是三叉树。第二问显然是树形动归。先说第二问。
F[i,k,0]表示节点i,不满意度为k,不修铁路的方案数。
F[i,k,1]表示节点i,不满意度为k,修1条铁路的方案数。
F[i,k,2]表示节点i,不满意度为k,修2条铁路的方案数。
记j为i的子节点
T1=F[j,k,0]+F[j,k,1] ->(i,j)修铁路
T2=F[j,k-1,0]+F[j,k-1,1]+F[j,k-1,2] ->(i,j)不修铁路
加法原理
则
F[i,k,2]=F[i,k,2]*T2+F[i,k,1]*T1
F[i,k,1]=F[i,k,1]*T2+F[i,k,0]*T1
F[i,k,0]=F[i,k,0]*T2
连乘是因为每个节点不一定只有一个子节点,而每个子节点的情况又是独立的,所以可以用乘法原理来做。
那么算法也十分明显了。
代码:
program design;
type
point=^node;
node=record
data:longint;
next:point;
end;
var
i,j:longint;
n,m,q:longint;
way,last:array[1..100100]of point;
pp:array[1..100100]of boolean;
ans:longint;
x,y:longint;
f:array[1..100100,0..12,0..2]of int64;
procedure build(x,y:longint);
begin
if way[x]=nil then begin
new(way[x]);
way[x]^.data:=y;
way[x]^.next:=nil;
last[x]:=way[x];
exit;
end;
new(last[x]^.next);
last[x]^.next^.data:=y;
last[x]^.next^.next:=nil;
last[x]:=last[x]^.next;
end;
procedure dfs(v:longint);
var
p:point;
now:longint;
t1,t2:int64;
begin
pp[v]:=true;
for i:=0 to ans do
begin
f[v,i,0]:=1;
f[v,i,1]:=0;
f[v,i,2]:=0;
end;
p:=way[v];
while p<>nil do
begin
now:=p^.data;
if not pp[now] then begin
dfs(now);
for i:=0 to ans do
begin
t1:=f[now,i,0]+f[now,i,1];
if i=0 then t2:=0
else t2:=f[now,i-1,0]+f[now,i-1,1]+f[now,i-1,2];
if t1 mod q<>0 then t1:=t1 mod q;
if t2 mod q<>0 then t2:=t2 mod q;
f[v,i,2]:=f[v,i,2]*t2+f[v,i,1]*t1;
f[v,i,1]:=f[v,i,1]*t2+f[v,i,0]*t1;
f[v,i,0]:=f[v,i,0]*t2;
for j:=0 to 2 do
if f[v,i,j] mod q<>0 then f[v,i,j]:=f[v,i,j] mod q;
end;
end;
p:=p^.next;
end;
end;
begin
assign(input,'design.in');
assign(output,'design.out');
reset(input);
rewrite(output);
readln(n,m,q);
if m<>n-1 then begin
writeln(-1);
writeln(-1);
close(input);
close(output);
halt;
end;
for i:=1 to n do
way[i]:=nil;
for i:=1 to m do
begin
readln(x,y);
build(x,y);
build(y,x);
end;
ans:=-1;
repeat
inc(ans);
fillchar(pp,sizeof(pp),false);
dfs(1);
until f[1,ans,0]+f[1,ans,1]+f[1,ans,2]>0; //用这个方法使得第一问和第二问同时做出。不过这样会带来一些麻
烦若mod q之后为0,它会误认为没有解,所以需要在每次取模的
时候判断是否为0
writeln(ans);
writeln((f[1,ans,0]+f[1,ans,1]+f[1,ans,2])mod q);
close(input);
close(output);
end.
志愿者招募
申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要N 天才能完成,其中第i 天至少需要Ai 个人。 布布通过了解得知,一共有M 类志愿者可以招募。其中第i 类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。
Input
第一行包含两个整数N, M,表示完成项目的天数和可以招募的志愿者的种类。 接下来的一行中包含N 个非负整数,表示每天至少需要的志愿者人数。 接下来的M 行中每行包含三个整数Si, Ti, Ci,含义如上文所述。为了方便起见,我们可以认为每类志愿者的数量都是无限多的。
Output
仅包含一个整数,表示你所设计的最优方案的总费用。
Sample Input
3 3
2 3 4
1 2 2
2 3 5
3 3 2
Sample Output
14
HINT
招募第一类志愿者3名,第三类志愿者4名 30%的数据中,1 ≤ N, M ≤ 10,1 ≤ Ai ≤ 10; 100%的数据中,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000,题目中其他所涉及的数据均 不超过2^31-1。
思路:
这道题是D1里最难的一道,当时赛场上只有高逸涵一人AC,题目难度由此可见。
我们先分析样例。
另X[i]为第i类志愿者招募个数。P[i]为第i天的志愿者个数,那么显然:
P[1]=X[1]>=2
P[2]=X[1]+X[2]>=3
P[3]=X[2]+X[3]>=4
为了消除不等号,我们引入Y[i]来表示不等式左右之差,那么:
P[1]=X[1]=2+Y[1]
P[2]=X[1]+X[2]=3+Y[2]
P[3]=X[2]+X[3]=4+Y[3]
我们再添两个式子
(1) P[0]=0=0;
(2) P[1]=X[1]=2+Y[1]
(3) P[2]=X[1]+X[2]=3+Y[2]
(4) P[3]=X[2]+X[3]=4+Y[3]
(5) P[4]=0=0;
我们用用2—5式分别减去前式得
P[1]-P[0]=X[1]=2+Y[1]
P[2]-P[1]=X[2]=1+Y[2]-Y[1]
P[3]-P[2]=-X[1]+X[3]=1+Y[3]-Y[2]
P[4]-P[3]=-X[2]-X[3]=-4-Y[3]
然后去掉左边P的表达式:
X[1]=2+Y[1]
X[2]=1+Y[2]-Y[1]
-X[1]+X[3]=1+Y[3]-Y[2]
-X[2]-X[3]=-4-Y[3]
整理得
X[1]-2-Y[1]=0
X[2]-1-Y[2]+Y[1]=0
-X[1]+X[3]-1-Y[3]+Y[2]=0
-X[2]-X[3]+4+Y[3]=0
看到这个我们想到了什么?没错,流量平衡!
对每个等式作为一个点,所有正值都为流入该点的流量,所有负值都为流出该点的流量。因为每个志愿者所能工作的日期为连续的一段,所以这样相邻相减可以保证每个X[i]只会出现两次,而且一次为负,一次为正,这便是这个题的妙处所在。
首先建立源点S,汇点T。如果i式出现了X[k],j式出现了-X[k],那么从j建一条边指向i,容量为无限大,费用为找第k类志愿者的费用。如果i式出现了Y[k],j式出现了-Y[k],那么从j建一条边指向i,容量为无限大,费用为0。如果出现了正常数,那么从S建一条费用为0,容量为这个正常数的边指向这个等式的点;如果出现了负常数,那么从这个等式的点建一条指向T的费用为0,容量为这个负常数绝对值的边。至此,建图完毕。剩下所要做的就是跑一边费用流就OK了。从构建方式来看,边数应为2n+m对于n+3个顶点来说为稀疏图,所以用普通SPFA来找最小增广路就OK,zwk费用流一般跑稠密图。(如果我说的不对,请各位神犇告诉我。。。)
代码:
program employee;
const
maxn=100000;
maxinf=maxlongint div 2;
type
point=^node;
node=record
data,cost,flow:longint;
next,back:point;
end;
var
i,j:longint;
pp:array[0..1010]of boolean;
quene:array[0..100010]of longint;
d:array[0..1010]of longint;
n,m:longint;
way,fa:array[0..1010]of point;
a,pre:array[0..1010]of longint;
l,r,c:array[0..10010]of longint;
s,t:longint;
ans:longint;
ff:longint;
function spfa:boolean;
var
i,head,tail,now:longint;
p:point;
begin
for i:=s to t do
begin
d[i]:=maxinf;
pp[i]:=false;
end;
d[s]:=0;
pp[s]:=true;
head:=0;
tail:=1;
quene[1]:=s;
repeat
head:=(head+1)mod maxn;
now:=quene[head];
p:=way[now];
while p<>nil do
begin
if (p^.flow>0)and(d[p^.data]>d[now]+p^.cost) then
begin
d[p^.data]:=d[now]+p^.cost;
pre[p^.data]:=now;
fa[p^.data]:=p;
if not pp[p^.data] then
begin
pp[p^.data]:=true;
tail:=(tail+1) mod maxn;
quene[tail]:=p^.data;
end;
end;
p:=p^.next;
end;
pp[now]:=false;
until head>=tail;
if d[t]=maxinf then exit(false)
else exit(true);
end;
procedure change;
var
i,min:longint;
begin
min:=maxinf;
i:=t;
repeat
if fa[i]^.flow<min then min:=fa[i]^.flow;
i:=pre[i];
until i=s;
i:=t;
repeat
dec(fa[i]^.flow,min);
inc(fa[i]^.back^.flow,min);
i:=pre[i];
until i=s;
ans:=ans+d[t]*min;
end;
procedure add(x,y,z,d:longint);
var
p:point;
begin
new(p); p^.data:=y; p^.flow:=d; p^.cost:=z; p^.next:=way[x]; way[x]:=p;
new(p); p^.data:=x; p^.flow:=0; p^.cost:=-z;p^.next:=way[y]; way[y]:=p;
way[x]^.back:=way[y];
way[y]^.back:=way[x];
end;
begin
assign(input,'employee.in');
assign(output,'employee.out');
reset(input);
rewrite(output);
readln(n,m);
fillchar(a,sizeof(a),0);
s:=0;
t:=n+2;
for i:=1 to n+1 do
way[i]:=nil;
for i:=1 to n do
begin
read(a[i]);
add(i,i+1,0,maxinf);
end;
for i:=1 to n+1 do
begin
ff:=a[i-1]-a[i];
if ff<0 then add(i,t,0,-ff)
else add(s,i,0,ff);
end;
for i:=1 to m do
begin
readln(l[i],r[i],c[i]);
add(r[i]+1,l[i],c[i],maxinf);
end;
ans:=0;
while spfa do
change;
writeln(ans);
close(input);
close(output);
end.