[BOI2010 bins]一个桶

【BOI链接】http://www.ut.ee/boi/
【题目大意】
按顺序给出n<=20000个箱子,他们的尺寸m<=1000。
求一个最大的k,使得前k个和第k+1个到2*k个箱子之间能一一匹配。
能匹配的定义是i∈[1,k] 且 j∈[k+1,2*k] 同时Size[i]【算法分析】
因为m<=1000,开一个1000的桶,然后从k从n/2开始枚举下来,维护一下。
由于O(nm)都不会超时,直接每次都暴力判断就好了。
【CODE】
#include #include #include int a[20001],t1[1001],t2[1001];

bool flag(){
int num1=0,num2=0,i;
for (i=1;i<=m;i++){
if (num1 num1+=t1[i];
num2+=t2[i];
if (num1 }
return true;
}

int main(){
int i,k,ans=0;
memset(t1,0,sizeof(t1));
memset(t2,0,sizeof(t2));
freopen("bins.in","r",stdin);
freopen("bins.out","w",stdout);
scanf("%d%d",&m,&n);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
k=n/2;
for (i=1;i<=k;i++) t1[a[i]]++;
for (i=k+1;i<=2*k;i++) t2[a[i]]++;
for (k=n/2;k>=0;k–){
if (flag()){
ans=k;
break;
}
if (!k) break;
t1[a[k]]–;
t2[a[k]]++;
t2[a[2*k]]–;
t2[a[2*k-1]]–;
}
printf("%dn",ans);
}

[BOI2010 pcb]贪心、平衡树

【题目和数据下载地址】http://www.ut.ee/boi/
【题目大意】
给定n条电线(Ai,Bi),其中A点都在一矩形箱子的上方,B点都在矩形箱子的下方。
其中Ai和Bi分别表示他们的横坐标。
然后这些电线交叉的话会短路,问至少要弄多少层(每层之间完全隔开),才能把这些电线全连上而又不短路。
数据保证Ai和Bi是2*n个不相同的整数。

【算法分析】
显然,假如对于一对电线(i,j),Ai我们先对电线按Ai从小到大排序,那么就变成了求把用最少的递增数列,把这个Bi数列全部分出来。
显然,假如Bi但是N<=100000。显然很难通过,因为边都有可能超过1000 0000。
于是我们可以贪心一下。
对于每个递增序列,记录它的最后一个就可以了,因为前面的已经和扩展无关了。
然后每一次对于一个Bi,在开了的ans个递增序列里找一个这样的序列i,满足:
Max(End[i]  :End[i]然后将Bi插到那个序列后面去。
遇过找不到这样一个序列,那么就新开一个序列,ans++,End[ans]=Bi。
最后输出ans即可。
然后对于上面那个找Max(End[i]  :End[i]【CODE】
#include #include #include const int N=100005;
struct Point{int x,y;}a[N];
int n,ans;

struct SBT_Type{
struct Node{int l,r,key,s;}tr[N*3];
int tot,root;
void update(int p){if (p) tr[p].s=tr[tr[p].l].s+tr[tr[p].r].s+1;}
int max(int x,int y){return x>y?x:y;}

void left(int &p){
int k=tr[p].r;
tr[p].r=tr[k].l;
tr[k].l=p;
update(p);
update(k);
p=k;
}

void right(int &p){
int k=tr[p].l;
tr[p].l=tr[k].r;
tr[k].r=p;
update(p);
update(k);
p=k;
}

void repair(int &p){
if (!p) return;
bool flag=false;
if (tr[tr[tr[p].l].l].s>tr[tr[p].r].s){
right(p);
flag=true;
}
if (tr[tr[tr[p].l].r].s>tr[tr[p].r].s){
left(tr[p].l);
right(p);
flag=true;
}
if (tr[tr[tr[p].r].r].s>tr[tr[p].l].s){
left(p);
flag=true;
}
if (tr[tr[tr[p].r].l].s>tr[tr[p].l].s){
right(tr[p].r);
left(p);
flag=true;
}
if (flag){
repair(tr[p].l);
repair(tr[p].r);
repair(p);
}
}

int del(int &p,int key){
tr[p].s–;
if (key int res=tr[p].key;
if (!tr[p].l || !tr[p].r) p=tr[p].l+tr[p].r;
else tr[p].key=del(tr[p].l,0x7FFFFFFF);
return res;
}
if (key else return del(tr[p].r,key);
}

void ins(int &p,int key){
if (!p){
p=++tot;
tr[p].l=tr[p].r=0; tr[p].s=1; tr[p].key=key;
return;
}
tr[p].s++;
if (key else ins(tr[p].r,key);
repair(p);
}

int Find(int p,int key){
if (!p) return -0x7FFFFFFF;
if (key>tr[p].key) return max(tr[p].key,Find(tr[p].r,key));
return Find(tr[p].l,key);
}
}SBT;

int cmp(const void *x,const void *y){
return ((Point*)x)->x-((Point*)y)->x;
}

int main(){
freopen("pcb.in","r",stdin);
freopen("pcb.out","w",stdout);
int i,tmp;
scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
qsort(a+1,n,sizeof(Point),cmp);
SBT.tot=SBT.root=ans=0;
for (i=1;i<=n;i++){
tmp=SBT.Find(SBT.root,a[i].y);
if (tmp==-0x7FFFFFFF){
ans++;
SBT.ins(SBT.root,a[i].y);
}
else{
SBT.del(SBT.root,tmp);
SBT.ins(SBT.root,a[i].y);
}
}
printf("%dn",ans);
}

[ZSOI 股票投资]包含贪心思想的动态规划

Description

Input

Output

对每组数据,输出一行,包含一个保留3位小数的实数,表示你能获得的最多现金。答案保证不会超过1e15。

Sample Input

1
5000 0.001 5 0.003
6
20 21 20 19 20 19

Sample Output

5332.000

【算法分析】
容易发现,如果是最优解的话,必然可以将时间划分成x个阶段,
每个阶段是由一个尽量买,和一个尽量卖所组成。
然后,
F[i]表示第i天最多剩多少现金

Gmax[i]表示第i天最多持有多少股
Grest[i]表示第i天持Gmax[i]股,剩下最多多少钱。
然后转移时就分当前位置是尽量买之后的和尽量卖之后的讨论。
维护一下就可以了。
【CODE】
#include #include #include const int N=30005;
const int INF=1000000000;
int n,tot;
double C,S1,S2,T,prize[N],F[N],Gmax[N],Grest[N];

inline double max(double x,double y){return x>y?x:y;}

double Buy(double x,double p){
int l=0,r=INF,mid;
double G;
while (l+1 mid=(l+r)/2;
G=p*mid*100;
if (G+G*T+max(G*S1,S2)<=x) l=mid;
else r=mid-1;
}
G=p*r*100;
if (G+G*T+max(G*S1,S2)<=x) return 100.0*r;
else return 100.0*l;
}

double Rest(double x,double b,double p){
double G=b*p;
return x-(G+G*T+max(G*S1,S2));
}

double Sell(double b,double r,double p){
double G=b*p;
return r+G-G*T-max(G*S1,S2);
}

void dp(){
int i;
double k;
memset(F,0,sizeof(F));
F[0]=C; Gmax[0]=0; Grest[0]=C;
for (i=1;i<=n;i++){
Gmax[i]=Gmax[i-1];
Grest[i]=Grest[i-1];
F[i]=max(F[i-1],Sell(Gmax[i],Grest[i],prize[i]));
k=Buy(F[i-1],prize[i]);
if (Gmax[i] Grest[i]=Rest(F[i],k,prize[i]);
Gmax[i]=k;
}
}
printf("%.3lfn",F[n]);
}

int main(){
freopen("stock.in","r",stdin);
freopen("stock.out","w",stdout);
int Tc;
scanf("%d",&Tc);
for (int i=1;i<=Tc;i++){
scanf("%lf%lf%lf%lf%d",&C,&S1,&S2,&T,&n);
for (int i=1;i<=n;i++) scanf("%lf",&prize[i]);
dp();
}
}

[ZSOI 简单数谜]DFS+调整搜索顺序

【题目】

Description

  很多人都曾经听说过数独,但你是否听说过数谜(Karuro)呢?实际上,数谜是数独的更大(且更难)的兄弟问题,而且在日本也是非常受欢迎的。
数谜问题和填字游戏类似,不过它要填的不是文字而是数字。数谜游戏的目标是用1-9填满所有空格,且这些数字相加的和满足相应的要求(或者称为“提 示”),且在同一栏(“栏”是指一些水平或者竖直的连续的空格,用于提示的格子不算空格)不能填重复的数字。当所有格子按要求被填满后,这个数谜就看作被 解决了。图1和图2是一个可能的数谜游戏示例。
当然,直接求解数谜问题的话会比较困难。所以现在我们需要解决的是一个更简单的数谜问题。简单数谜的形状是一个(n+1)行乘(m+1)列的矩 形。而简单数谜也只有两种要求,就是行要求和列要求,且分别处于第一行和第一列,其他格子则是空格,而左上角是忽略不计的。coolzzz同学爱好简单数 谜,他已经给一些简单数谜填好了其中的一些空格。现在,他想寻求你的帮助,来帮他完成这些简单数谜。如图3所示,2和9是coolzzz同学已经填好的空 格,图4则是一个基于图3 的一个可能的解答。

Input

  输入包含多组测试数据。第一行包含一个正整数T,表示测试数据数目。每组数据第一行是n(n<10)和m(m<10),表示数谜的形状的 大小。接下来一行有n个整数,是相应的行要求;然后一行是m个整数,是相应的列要求。接下来的n行每行有m个小于10的非负整数,0表示该空格还没有被填 数字,其他表示coolzzz同学已经填好的数字。输入数据保证未填数字的空格不会超过16个。

Output

  对于每组测试数据,输出若干行。如果基于coolzzz已填的结果,该数谜只有一个解,则输出该解;如果不止一个解,则输出一行“Not unique.”;如果没有解,则输出一行“No answer.”。

Sample Input

3
3 3
6 6 6
6 6 6
0 0 0
0 3 0
0 0 0
2 3
10 17
5 16 6
2 0 0
0 9 0
2 2
3 5
4 4
0 0
0 0

Sample Output

Not unique.
2 7 1
3 9 5
No answer.

【算法分析】
类似数独的搜索题,然后就是设一个简单的估价函数,按它的值排下序,然后再用位运算优化一下就可以很快出解。
另外传说的DLX应该会有更好的表现。。。
【CODE】
#include #include #include const int N=10;
const double rate=0.02;
struct LL{int x,y;double w;}L[N*N];
int n,m,tot,ans;
int limitx[N],limity[N],canx[N],cany[N],donex[N],doney[N];
int a[N][N],lg[2000],ansl[N][N];
bool flag;

void input(){
memset(donex,0,sizeof(donex));
memset(doney,0,sizeof(doney));
flag=true;
tot=0;
scanf("%d%d",&m,&n);
for (int i=0;i for (int i=0;i for (int i=0;i for (int i=0;i for (int i=0;i for (int j=0;j scanf("%d",&a[i][j]);
if (a[i][j]){
limitx[i]-=a[i][j];
limity[j]-=a[i][j];
if (canx[i]&(1< if (cany[j]&(1< if (limitx[i]<0) flag=false;
if (limity[j]<0) flag=false;
canx[i]^=1< cany[j]^=1< donex[i]++;
doney[j]++;
}else{
tot++;
L[tot].x=i;
L[tot].y=j;
}
}
}

int cmp(const void *x,const void *y){
if (((LL*)x)->w>((LL*)y)->w) return 1;
if (((LL*)x)->w<((LL*)y)->w) return -1;
if (((LL*)x)->w==((LL*)y)->w) return 0;
}

void init(){
for (int i=1;i<=tot;i++)
L[i].w=donex[L[i].x]+doney[L[i].y]+rate*(limitx[L[i].x]+limity[L[i].y]);
qsort(&L[1],tot,sizeof(LL),cmp);
}

inline int sqr(int x){return x*(x+1)/2;}

void dfs(int dep){
LL &Node=L[dep];
if (dep==tot+1){
ans++;
if (ans>1) return;
memcpy(ansl,a,sizeof(a));
}
int state=canx[Node.x]&cany[Node.y],p;
while (state){
p=state&-state;
if (limitx[Node.x]-lg[p] if (limity[Node.y]-lg[p] a[Node.x][Node.y]=lg[p];
limitx[Node.x]-=lg[p];
limity[Node.y]-=lg[p];
donex[Node.x]++;
doney[Node.y]++;
canx[Node.x]-=p;
cany[Node.y]-=p;
if (!(donex[Node.x]==n && limitx[Node.x]!=0))
if (!(doney[Node.y]==m && limity[Node.y]!=0))
dfs(dep+1);
if (ans>1) return;
a[Node.x][Node.y]=0;
limitx[Node.x]+=lg[p];
limity[Node.y]+=lg[p];
donex[Node.x]–;
doney[Node.y]–;
canx[Node.x]+=p;
cany[Node.y]+=p;
state-=p;
}
}

void solve(){
if (ans>1) printf("Not unique.n");
if (ans==0) printf("No answer.n");
if (ans==1)
for (int i=0;i for (int j=0;j printf("%d",ansl[i][j]);
if (j==n-1) printf("n");
else printf(" ");
}
}

int main(){
lg[1]=0;
for (int i=2;i<=1024;i++){
lg[i]=lg[i-1];
if (i==1< }
for (int i=1;i<=1024;i++) lg[i]++;
freopen("kakuro.in","r",stdin);
freopen("kakuro.out","w",stdout);
int Tc;
scanf("%d",&Tc);
for (int i=1;i<=Tc;i++){
input();
if (!flag){
printf("No answer.n");
continue;
}
ans=0;
init();
dfs(1);
solve();
}
}

[ZSOI 三核苷酸]求一个特殊的方差

【题目】

Description

  三核苷酸是组成DNA序列的基本片段。具体来说,核苷酸一共有4种,分别用’A’,’G’,’C’,’T’来表示。而三核苷酸就是由3个核苷酸排列而 成的DNA片段。三核苷酸一共有64种,分别是’AAA’,’AAG’,…,’GGG’。给定一个长度为L的DNA序列,一共可以分辨出(L-2)个三核 苷酸。现在我们想用一些统计学的方法来进行一些分析,步骤如下:

给定一个DNA序列,请你计算出它的方差。

Input

  输入包含多组测试数据。第一行包含一个正整数T,表示测试数据数目。每组数据包含一个由’A’,’G’,’C’,’T’组成的字符串,代表要统计的 DNA序列。DNA序列的长度大于等于3且不会超过100000。

Output

  对每组测试数据,输出一行答案,为一个保留6位精度的实数,代表S2的值。如果你的答案和标准答案的“相对误差”小于1e-8,你的答案会被视为正确 的答案。

Sample Input

1
ATATATA

Sample Output

0.750000

【算法分析】
由于只有64种那个XX,所以开一个大小为64的桶,并将之与4进制数对应起来。
然后就是类似dp那样,根据前面的信息,推出新的信息。
【其他】
由于精度过紧,而我又不会用C++的long double类型的scanf读入和printf输出。。。
于是果断Pascal。。。
【CODE】
const
inf=’tri.in’;
ouf=’tri.out’;

var
Tc,n:longint;
p,total:array [0..64] of extended;
num,ss:array [0..64] of extended;
str:ansistring;
a:array [1..200000] of longint;

procedure init;
var
i:longint;
begin
for i:=1 to n do
case str[i] of
‘A’:a[i]:=0;
‘T’:a[i]:=1;
‘G’:a[i]:=2;
‘C’:a[i]:=3;
end;
end;

procedure solve;
var
i,now:longint;
sum,times,pj:extended;
begin
sum:=0; times:=0;
fillchar(num,sizeof(num),0);
fillchar(total,sizeof(total),0);
fillchar(p,sizeof(p),0);
for i:=n-2 downto 1 do
begin
now:=a[i]*4+a[i+1];
now:=now*4+a[i+2];
if p[now]=0 then
begin
total[now]:=1;
p[now]:=i;
num[now]:=0;
end
else
begin
sum:=sum+total[now]*(p[now]-i)+num[now];
times:=times+total[now];

num[now]:=total[now]*(p[now]-i)+num[now];
total[now]:=total[now]+1;
p[now]:=i;
end;
end;
if times=0 then
begin
sum:=0;
writeln(sum:0:6);
exit;
end;
pj:=sum/times;
fillchar(num,sizeof(num),0);
fillchar(total,sizeof(total),0);
fillchar(p,sizeof(p),0);
fillchar(ss,sizeof(ss),0);
sum:=0;
for i:=n-2 downto 1 do
begin
now:=a[i]*4+a[i+1];
now:=now*4+a[i+2];
if (p[now]=0) then
begin
total[now]:=1;
p[now]:=i;
num[now]:=0;
ss[now]:=0;
end
else
begin
num[now]:=num[now]
+sqr(p[now]-i)*(total[now]-1)
+2*ss[now]*(p[now]-i)+sqr(p[now]-i-pj);

ss[now]:=ss[now]
+(total[now]-1)*(p[now]-i)
+p[now]-i-pj;

total[now]:=total[now]+1;

p[now]:=i;

sum:=sum+num[now];
end;
end;
writeln(sum/times:0:6);
end;

procedure main;
var
i:longint;
begin
readln(Tc);
for i:=1 to Tc do
begin
readln(str);
n:=length(str);
init;
solve;
end;
end;

begin
assign(input,inf);
assign(output,ouf);
reset(input);
rewrite(output);
main;
close(input);
close(output);
end.