[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.

[ZSOI 生成树]组合数学

【题目】

Description

  有一种图形叫做五角形圈。一个五角形圈的中心有1个由n个顶点和n条边组成的圈。在中心的这个n边圈的每一条边同时也是某一个五角形的一条边,一共有 n个不同的五角形。这些五角形只在五角形圈的中心的圈上有公共的顶点。如图0所示是一个4-五角形圈。
现在给定一个n五角形圈,你的任务就是求出n五角形圈的不同生成树的数目。还记得什么是图的生成树吗?一个图的生成树是保留原图的所有顶点以及顶 点的数目减去一这么多条边,从而生成的一棵树。
注意:在给定的n五角形圈中所有顶点均视为不同的顶点。

Input

输入包含多组测试数据。第一行包含一个正整数T,表示测试数据数目。每组测试数据包含一个整数n( ),代表你需要求解的五角形圈中心的边数。

Output

对每一组测试数据,输出一行包含一个整数x,表示n五角形圈的生成树数目模2007之后的结果。

Sample Input

1
2

Sample Output

40

【算法分析】
把每一个五边形当成一个状态,并按顺时针进行编号。
那么第i个状态和其它状态连接的就只有在中心圈中对应的两个点。
然后我们考虑最终必然是有且仅有一个状态是中心圈对应的点并非通过该五边形连接的。
这个特殊的状态必然是去掉圈上边和剩下四条边的任意一条。所以有4种删边法。
除了这个状态以后,其它状态都是独立的,并且应当只去掉1条边。
一共五条边,所以就是有5种情况。
所以,根据乘法原理,答案应该是4*5^(n-1)。
又由于那个特殊的状态可以分步在n个状态中的任意一个,并且所得结果必然都是不同的。
所以最终结果:4*n*5^(n-1)。
【CODE】
#include #include #include const int mod=2007;
int n,ans;

void solve(){
ans=1;
for (int i=1;i ans=ans*4%mod;
ans=ans*n%mod;
printf("%dn",ans);
}

int main(){
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
int Tc;
scanf("%d",&Tc);
for (int i=1;i<=Tc;i++){
scanf("%d",&n);
solve();
}
}

[SPOJ412 COVER]特殊图的最小费用流巧妙优化

【题目地址】https://www.spoj.pl/problems/COVER/
【题目大意】
有n个点,m条有向边,这m条有向边的权值w=cost[x]+cost[y],求k条边满足:
1、这k条边之间没有共起点和共终点的
2、这k条边的权和最小
【算法分析】
容易建图:
将n个点每个点拆成(i,i’)
然后对于有向边(x,y),连边(x,y’),该边权为cost[x]+cost[y]
于是相当于对这个二分图,求k次最小费用流的增广。
然后由于m太大,我们需要更快的算法。
对于观察每次增广,实际上就是匈牙利算法中的找交错轨,
并且新增的权总为增广路中的cost[起点]+cost[终点]
然后我们可以利用这个性质,将cost排序,然后有:当终点一样的情况下,让前面的先匹配比较好。
所以一个点只用进去一次就可以了。
最后就是每次枚举从某一点出发找增广路,找出本次增广增加的费用最小的。
然后去实现这个增广即可。
。。。罗嗦了这么多,结合程序看吧= =
【其它】
时间复杂度O(k*(n+m))
【CODE】
#include #include #include const int N=10005;
const int INF=1000000000;
struct gtp{int y,next;}g[1001111];
int n,m,k,ans,times;
int ls[N],link[N],v[N],cost[N][2],zz[N],To_y[N],cx[N];

void Read(int &x){
char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
for (x=0;ch>=’0′ && ch<='9';ch=getchar())
x=x*10+ch-‘0’;
}

int cmp(const void *x,const void *y){return ((int*)x)[0]-((int*)y)[0];}

void init(){
memset(ls,0,sizeof(ls));
memset(link,0,sizeof(link));
memset(cx,0,sizeof(cx));
Read(k); Read(n); Read(m);
for (int i=1;i<=n;i++){
Read(cost[i][0]);
cost[i][1]=i;
}
qsort(&cost[1][0],n,sizeof(int)*2,cmp);
for (int i=1;i<=n;i++) zz[cost[i][1]]=i;
for (int i=1,x,y;i<=m;i++){
Read(x); Read(y);
x=zz[x]; y=zz[y];
g[i].y=y; g[i].next=ls[x]; ls[x]=i;
}
}

int Try(int p){
int res=INF,tmp;
for (int t=ls[p];t;t=g[t].next)
if (!v[g[t].y]){
v[g[t].y]=1;
if (!link[g[t].y]) tmp=cost[g[t].y][0];
else tmp=Try(link[g[t].y]);
if (tmp res=tmp;
To_y[p]=g[t].y;
}
}
return res;
}

void expand(int p){
int q=link[To_y[p]];
link[To_y[p]]=p;
if (q) expand(q);
}

void solve(){
ans=0;
for (int i=1;i<=k;i++){
memset(v+1,0,sizeof(int)*n);
memset(To_y+1,0,sizeof(int)*n);
int now=INF,nowi,tmp;
for (times=1;times<=n;times++)
if (cx[times]==0){
tmp=cost[times][0]+Try(times);
if (tmp now=tmp;
nowi=times;
}
}
if (now==INF){
printf("NONEn");
return;
}
ans+=now;
expand(nowi);
cx[nowi]=1;
}
printf("%dn",ans);
}

int main(){
int Tc;
Read(Tc);
for (int i=1;i<=Tc;i++){
init();
solve();
}
}