今天来总结一套接近一个月之前(8月17日)做的题- -,因为各种各样的理由忘了总结,现在补上……不过做题时的感觉都忘了,只能好好回顾回顾了。
T1:Magical GCD
题目大意:对于一个由正整数组成的序列,Magical GCD是指一个区间的长度乘以该区间内所有数字的最大公约数。给出T个含有N个数的序列(N<=100000,T<=20,每个数<=10^12),求出这个序列最大的Magical GCD。
做这道题的时候好像是一点想法都没有……果断暴力了……
如果我们考虑一个区间 [l,r] 内所有数的最大公约数,如果这个区间越来越大,那么最大公约数只能是越来越小。因此,我们可以考虑将这N个数划分成若干个区域,此时每个区域内的数都是相同的。然后我们合并这些区域,可以发现新区域的最大公约数可以秒解,区间长度也可以通过合并得到。在不断的合并中我们就可以遍历完所有可能的答案了,找答案的最大值就可以了。
因为合并过程中最多会有log级别的区域,因此时间复杂度为O(T*NlogK),其中K为N个数中最大的数。
var
ans:int64;
t,n,i,j,c:longint;
a:array[1..100000] of int64;
f:array[1..100000,1..2] of longint;
function max(x,y:int64):int64; begin if x>y then exit(x) else exit(y); end;
function gcd(a,b:int64):int64; begin if b=0 then exit(a) else exit(gcd(b,a mod b)); end;
procedure add(x,y:longint);
begin
inc(c);
a[c]:=gcd(a[x],a[y]);
f[c,1]:=f[x,1];
f[c,2]:=f[y,2];
ans:=max(ans,a[c]*(f[c,2]-f[c,1]+1));
end;
begin
assign(input,'magicgcd.in');reset(input);
assign(output,'magicgcd.out');rewrite(output);
readln(t);
for t:=1 to t do begin
readln(n);
ans:=1;
for i:=1 to n do begin
read(a[i]);
f[i,1]:=i;
f[i,2]:=i;
ans:=max(ans,a[i]);
end;
while n>1 do begin
j:=1;
c:=0;
for i:=2 to n do
if a[j]<>a[i] then begin
if i=j+1 then add(j,i)
else begin
add(j,i-1);
add(i-1,i);
end;
j:=i;
end;
if j
T2:Multiset
题目大意:有个集合,初始只有一个数0。对于集合中的每个数x有下列3个操作:
1、x = x + 1;
2、x = y + z(x分裂为两个自然数y、z);
3、什么都不做
对集合中所有的数都选择一个操作执行,称为对集合进行了“一轮”操作。现在给出最终集合中的N个数(N<=10^6,x<=10^6),求最少已经进行了几轮操作。
考试我对这道题很有想法,花了很长的时间做,如愿AC。
因为给出的3种操作都是可逆的,所以我们可以考虑对最终集合“倒着”操作。现在问题当然就是如何选择操作才能使轮数最少。
如果有任意两个数是0,那么当前这一轮就合并所有的0就行了。关键的问题是:对于一些非0的数,是先和其他的数(包括0)合并,还是减1?仔细想想就可以知道,选择减1较优,因为如果两个非0的数各自减1后合并,2轮就可以完成;如果这两个数先合并再减2,就需要3轮了。因为合并两个数不能让两个数同时执行减1操作。
因此,对于所有非0的数,我们不断减1,当减到0时就和其他的0合并,如此操作直到集合只剩下一个0为止。此时所花费的轮数就是答案了。
可以发现这样模拟的最坏时间复杂度为O(Ans*N),其中Ans为答案。看起来会爆,但因为我们最多只能进行Max(x)+log(N)轮操作,而每次操作时真正要修改的数也只是不断减少,所以还是很快的。
PS:因为模拟本质还是减数,所以可以直接用桶存下所有的数再统计轮数,数学方法秒杀模拟。本人太懒,就把DengWx的程序搬过来吧……
var
a:array[0..1000010] of longint;
n,i,j,c,m,t,s,ans:longint;
procedure qsort(x,y:longint);
var
i,j,m:longint;
begin
if x>=y then exit;
i:=x;
j:=y;
m:=a[random(y-x+1)+x];
repeat
while a[i]
m do dec(j);
if i<=j then begin
a[0]:=a[i];
a[i]:=a[j];
a[j]:=a[0];
inc(i);
dec(j);
end;
until i>j;
qsort(x,j);
qsort(i,y);
end;
begin
randomize;
assign(input,'multiset.in');reset(input);
assign(output,'multiset.out');rewrite(output);
readln(n);
for i:=1 to n do read(a[i]);
qsort(1,n);
m:=1;
while (m<=n) and (a[m]=0) do inc(m);
c:=m-1;
ans:=0;
s:=0;
while (c>1) or (m<=n) do begin
inc(ans);
c:=c div 2+c mod 2;
t:=a[m]-s;
s:=a[m];
j:=0;
while (m<=n) and (a[m]-s=0) do begin
inc(j);
inc(m);
end;
{for i:=i to n do begin
dec(a[i],t);
if a[i]=0 then inc(j);
if (a[i]>0) and (m=0) then m:=i;
end;
if m=0 then m:=n+1;}
for i:=1 to t-1 do c:=c div 2+c mod 2;
c:=c+j;
if t>0 then inc(ans,t-1);
end;
writeln(ans);
close(input);close(output);
end.
DengWx的源代码:
#include
#include
using namespace std;
const int N = 1000010;
int n, a[N], b[N], maxn;
int main(){
ios::sync_with_stdio(0);
freopen("multiset.in","r",stdin);
freopen("multiset.out","w",stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]), ++ b[a[i]], maxn = maxn < a[i] ? a[i] : maxn;
int ans(b[0]);
for (int i = 1; i <= maxn; ++ i){
ans -= ans / 2; ans += b[i];
}
while (ans - 1) ans -= ans / 2, ++ maxn;
printf("%d", maxn);
return 0;
}
T3:组队
题目大意:给出N个人之间的关系(N<=100),只有认识与不认识两种,且为单向认识,无传递性。现在要将这些人分成两组,要求每个组中至少有1个人,每个组中人都互相认识,且两组之间的人数差最小。输出其中一种分法。
做这道题时也没什么想法,直接暴力搞起……
其实给出的这张关系图是很明显的二分图,如果不是二分图那就意味着没有任何一种合法的分法,无解。对这张图染色后就可以使用动态规划方程找方案了。
还是具体点说:如果两个人之间只要有一个人不认识另一个人,那么这两个人就必须分开在两个组里,因此只要不是互相认识的两个人u,v,我们就给这两个人连一条无向边。这样构成的新图中两个相邻的点都必须被染上不同的颜色(一共只有2种颜色)。因此使用深搜尝试给每个点染色,如果发现染色冲突,那么就是无解的情况了。
这样一来,我们将图中的每个连通块都染上了颜色,每个连通块中的相同颜色的点要被放在同一组中。现在我们考虑——应该怎么分配这些点呢?
因为每个连通块中不同颜色的点数差是已知的(深搜时可以顺便统计一下),那么我们只要枚举其中一个颜色的点放在哪一组,那么两个组之间的点数差是可以马上知道的。那么此时就要动态规划了:F[i][j]表示将第i个连通块中的点放入两个组后,两组间点数差为j的方案是否存在(注意,j是可以为负数的)。那么转移是显然的:只要f[i-1,j±q[i]]为true(q[i]为第i个连通块中不同颜色的点数差)那么f[i,j] = true。此时只要再开一个g[i,j]记录f[i,j]是从哪个状态转移来的,就构成了一条链。此时枚举0<=j<=n,当f[n,±j]为true时就代表两组间点数差为j的方案存在,根据g的记录就可以还原所有分配操作,从而得到两组具体的答案了。
时间复杂度最坏O(N^2)。
var
a:array[1..100,1..100] of boolean;
pd:array[1..100] of boolean;
f,l:array[0..100,-200..200] of boolean;
b,p,q,ans1,ans2:array[0..100] of longint;
n,i,j,k,c:longint;
procedure dye(v:longint);
var
i,nxt:longint;
begin
p[v]:=c;
nxt:=3-b[v];
if b[v]=1 then inc(q[c]) else dec(q[c]);
for i:=1 to n do
if a[v,i] then begin
if b[v]=b[i] then begin
writeln(-1);
close(input);close(output);
halt;
end;
if b[i]=0 then begin
b[i]:=nxt;
dye(i);
end;
end;
end;
procedure makeans(c,v:longint);
var
i:longint;
begin
for i:=1 to n do
if p[i]=c then begin
if b[i]=v then begin
inc(ans1[0]);
ans1[ans1[0]]:=i;
end
else begin
inc(ans2[0]);
ans2[ans2[0]]:=i;
end;
end;
end;
begin
assign(input,'makegroup.in');reset(input);
assign(output,'makegroup.out');rewrite(output);
readln(n);
for i:=1 to n do begin
fillchar(pd,sizeof(pd),1);
pd[i]:=false;
read(k);
while k>0 do begin
pd[k]:=false;
read(k);
end;
readln;
for j:=1 to n do
if pd[j] then begin
a[i,j]:=true;
a[j,i]:=true;
end;
end;
c:=0;
for i:=1 to n do
if b[i]=0 then begin
inc(c);
b[i]:=1;
dye(i);
end;
f[0,0]:=true;
for i:=1 to c do
for j:=-n to n do
if f[i-1,j+q[i]] then begin
f[i,j]:=true;
l[i,j]:=true;
end
else if f[i-1,j-q[i]] then f[i,j]:=true;
for i:=0 to n do
if f[c,i] then begin
k:=i;
for j:=c downto 1 do
if l[j,k] then begin
makeans(j,2);
inc(k,q[j]);
end
else begin
makeans(j,1);
dec(k,q[j]);
end;
break;
end
else if f[c,-i] then begin
k:=-i;
for j:=c downto 1 do
if l[j,k] then begin
makeans(j,2);
dec(k,q[j]);
end
else begin
makeans(j,1);
inc(k,q[j]);
end;
break;
end;
write(ans1[0],' ');
for i:=1 to ans1[0] do write(ans1[i],' ');
writeln;
write(ans2[0],' ');
for i:=1 to ans2[0] do write(ans2[i],' ');
writeln;
close(input);close(output);
end.
总结:这次比赛我得到了130分,被樊神完爆了……T1和T3的方案咋一看很简单,但想出来却很不容易。比赛中我好像没犯什么重大错误,因此得分比较正常。还是要加油啊,总是被低年级的大神虐也不是什么好事对不对^_^。