[ZOJ3334 Body Check]GXX神牛的美妙解法:解不等式

【题目大意】
给定n个工作,m条通道。
然后这m条通道要么全部一起完成工作,要么只有1条在完成工作,求把n个工作全部做完至少需要多长时间。
【算法分析】
http://hi.baidu.com/pojoflcc/blog/item/a5e757816511ccdb9123d9b4.html
Lcc大神也写了解题报告,但是他的方法思路过于繁杂。。。虽然也可以做到排序的复杂度。
实际上,我们就是要让n个工作的并行时间尽可能地长。

姑且设这个并行时间为ans。
本题容易得到一个二分ans的算法。
具体是:
先二分ans。
然后将大于ans的工作时间置为ans。最后判定所有工作时间的和是否>=ans*m
是,则可行,否则不可行。
(想象你在填一个m*ans的表,就很容易理解)

然后现在根据GXX神牛的指示。
实际上不需要二分答案。

我们现在来看。假设我们将工作时间Ai已经按降序排列。
并再次假设A[i]<=ans<=A[i+1]
那么类似二分的判定的地方得到一个不等式
S[i+1,n]+i*ans>=m*ans     (S[i,j]表示从a[i]到a[j]的和)
于是我们整理一下,得出3个不等式:
ans>=A[i]           ——————①
ans<=A[i+1]       ——————②
ans<=S/(m-i)     ——————③
然后解它们看在这个区间有无解就行了。
可能大家觉得第③式有点问题。就是那个(m-i)的正负不知道,所以不等式不知道要不要变号。
实际上,在i递增到m=i时,必然就有解,所以不会出现m于是问题得到完美解决。
【时间复杂度】排序O(n lg n)+处理O(n)
【空间复杂度】O(n)
【其它】
1、深深地YM GXX神牛
2、注意到那个二分的解法,本题精度要求是1e-9,所以很容易挂。。。当然也是有可能AC的。
【CODE】
#include #include #include #define AA (*((double*)A))
#define BB (*((double*)B))
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
const int N=1005;
int m,n;
double a[N],s[N],ans,x;

void solve(){
ans=0;
a[n+1]=s[n+1]=0;
a[0]=1e20;
for (int i=n;i>=1;i–) s[i]=s[i+1]+a[i];
for (int i=0;i<=n;i++)
if (i==m){
ans=a[i];
break;
}else
if (m>i){
x=min(a[i],s[i+1]/(m-i));
if (x>=a[i+1]){
ans=x;
break;
}
}
printf("%.9lfn",s[1]-ans*m+ans);
}

int cmp(const void *A,const void *B){
if (AA if (AA>BB) return -1;
return 0;
}

int main(){
while (scanf("%d%d",&m,&n)!=EOF){
for (int i=1;i<=n;i++) scanf("%lf",&a[i]);
qsort(a+1,n,sizeof(double),cmp);
solve();
}
}

[JSOI2010 Frozen Nova 冷冻波]二分答案、最大流、还有一些琐碎的几何判定

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1822
【算法分析】
非常裸的模型。
求圆到线段的距离可以利用海伦公式+勾股定理。
然后剩下的就是二分时间+一个二分图匹配了。因为巫妖可能发多次,所以用网络流实现。
【其它】之前WA了∞遍,今天重敲。。。我靠,1Y。
【CODE】
#include #include #include #include #include #define sqr(x) ((x)*(x))
#define eps 1e-6
using namespace std;
const int maxn=205*2;
int Li,Wi,Tr;
bool v[maxn][maxn];
struct Lich_t{int x,y,r,t;}Lich[maxn];
struct Wise_t{int x,y;}Wise[maxn];
struct Tree_t{int x,y,r;}Tree[maxn];
struct Graph_t{
struct edge{int x,y,c;edge *next,*op;}g[maxn*maxn*4],*ls[maxn],*cur[maxn],*fa[maxn];
int d[maxn],gap[maxn],Flow,e,S,T;

void clr(){e=0; memset(ls,0,sizeof(ls));}

void add(int x,int y,int c){
g[++e].x=x; g[e].y=y; g[e].c=c; g[e].next=ls[x]; ls[x]=&g[e]; g[e].op=&g[e+1];
g[++e].x=y; g[e].y=x; g[e].c=0; g[e].next=ls[y]; ls[y]=&g[e]; g[e].op=&g[e-1];
}

void relabel(int p){
cur[p]=ls[p];
int mm=T;
for (edge *t=ls[p];t;t=t->next)
if (t->c && d[t->y] mm=d[t->y];
d[p]=mm+1;
}

void change(){
Flow++;
for (int i=T;i!=S;i=fa[i]->x){
fa[i]->c–;
fa[i]->op->c++;
}
}

void sap(){
int i;
Flow=0;
for (i=1;i<=T;i++){
cur[i]=ls[i];
d[i]=0;
gap[i]=0;
}
gap[0]=T;
i=S;
while (d[S] for (edge *&t=cur[i];t;t=t->next)
if (t->c && d[t->y]+1==d[t->x]) break;
if (cur[i]){
fa[cur[i]->y]=cur[i];
i=cur[i]->y;
if (i==T){
change();
i=S;
}
}else{
if (!–gap[d[i]]) break;
relabel(i);
++gap[d[i]];
if (i!=S) i=fa[i]->x;
}
}
}
}G;

inline int dis(int &X1,int &Y1,int &X2,int &Y2){return sqr(X1-X2)+sqr(Y1-Y2);}
inline double S(double &a,double &b,double &c){
double p=(a+b+c)/2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}

bool Can(Lich_t L,Wise_t W){
if (dis(L.x,L.y,W.x,W.y)>sqr(L.r)) return false;
for (int i=1;i<=Tr;i++){
Tree_t &T=Tree[i];
double a,b,c,d;
a=sqrt(dis(W.x,W.y,T.x,T.y));
b=sqrt(dis(W.x,W.y,L.x,L.y)); if (b==0) b=eps;
c=sqrt(dis(L.x,L.y,T.x,T.y));
if (b*b+c*c>a*a && a*a+b*b>c*c) d=S(a,b,c)*2/b;
else d=min(a,c);
if (d+eps<=T.r) return false;
}
return true;
}

void init(){
ios::sync_with_stdio(false);
memset(v,false,sizeof(v));
cin >> Li >> Wi >> Tr;
for (int i=1;i<=Li;i++)
cin >> Lich[i].x >> Lich[i].y >> Lich[i].r >> Lich[i].t;
for (int i=1;i<=Wi;i++)
cin >> Wise[i].x >> Wise[i].y;
for (int i=1;i<=Tr;i++)
cin >> Tree[i].x >> Tree[i].y >> Tree[i].r;
for (int i=1;i<=Li;i++)
for (int j=1;j<=Wi;j++)
if (Can(Lich[i],Wise[j]))
v[i][j]=true;
}

bool Try(int Limit){
G.clr(); G.S=Li+Wi+1; G.T=Li+Wi+2;
for (int i=1;i<=Li;i++)
G.add(G.S,i,Limit/Lich[i].t+1);
for (int i=1;i<=Wi;i++)
G.add(Li+i,G.T,1);
for (int i=1;i<=Li;i++)
for (int j=1;j<=Wi;j++)
if (v[i][j]) G.add(i,Li+j,1);
G.sap();
if (G.Flow==Wi) return true;
return false;
}

void solve(){
int l=0,r=1000000000,mid;
while (l mid=(l+r)/2;
if (Try(mid)) r=mid;
else l=mid+1;
}
if (!Try(l)) cout << "-1n";
else cout << l << "n";
}

int main(){
init();
solve();
return 0;
}

[USACO2010 Hol Gold 【cowpol】](性质证明、LCA) Or (树的分治A7个点)

【题目链接】http://ace.delos.com/ioiupload?init=1&a=nejLCFTaRn6
或者八中:http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1776
不过八中由于是Windows,所以要手写栈~没事可以去写一写~
Pascal的话就不用怕了,直接开编译开关:{$M 10000000}
【题目大意】
n<=20 0000
树上的每个点都涂了一种颜色,
对于每种颜色,问属于这种颜色的点中的最远点对是多少?
边权全是1。
时限2s per data
【算法分析】
做法1:
一开始看比较直观的就是对树进行基于点的分治。
时间复杂度O(n lg n)。
空间复杂度O(n lg n)。
但是由于常数过大,终究逃脱不了TLE的命运。

做法2:
首先我们画出一棵树。
然后可以发现,无论怎么画,对于同一种颜色,最长路出现时,其中一个点必然是这颗树中深度最大的点。
下面给出我的证明:
设x,y,z为三个同颜色的点,x为所有该种颜色中的深度最大的一个点。
那么我们现在要证命题:dis(y,z)<=max(dis(x,y),dis(x,z))。

可以用这个图先直观地感受一下。
下面我们采用反证法。
题设:dis(y,z)>max(dis(x,y),dis(x,z))。
将这个不等式转化成一个不等式组:
dep[x]+dep[y]-dep[LCA(x,y)]*2dep[x]+dep[z]-dep[LCA(x,z)]*2dep[x]-dep[z]<(dep[LCA(x,y)]-dep[LCA(y,z)])*2  ————①
dep[x]-dep[y]<(dep[LCA(x,z)]-dep[LCA(y,z)])*2  ————② 我们对这个两个不等式进行放大,得:
dep[LCA(x,y)]-dep[LCA(y,z)]>0
dep[LCA(x,z)]-dep[LCA(y,z)]>0
(之所以可以这样做,是因为dep[x]>=dep[z] && dep[x]>=dep[y])

下面进行分类讨论:
1、dep[LCA(x,y)]<=dep[LCA(y,z)]。那么①式显然不成立。矛盾。
2、dep[LCA(x,y)]>dep[LCA(y,z)]。那么z的位置有两种可能。
①z是y的子孙。这样显然与题设矛盾。因为dis(x,z)>=dis(y,z)。(x到z的路径包括了y到z的路径)
②LCA(y,z)在“LCA(x,y)到y”的路径上。那么在这种情况下,我们看②式。

显然,此时LCA(x,z)=LCA(y,z),所以②式不可能成立。于是又与题设矛盾。
所以,综上所述,①②两式不可能同时成立。于是题设不可能成立。
于是反证出命题:dis(y,z)<=max(dis(x,y),dis(x,z)) 正确。 有这个了结论,那么就可以找出每种颜色里深度最大的。然后其它的点都和它算一下距离就可以了。这里面用到了最近公共祖先(LCA)的经典算法。
【时间复杂度】O(n)
【空间复杂度】O(n)
【其它】这个代码在衡阳八中A不了,因为会栈溢出。
【时间和空间状况】
> Run 1: OK [0.011 secs, 8600 KB]
> Run 2: OK [0.011 secs, 8600 KB]
> Run 3: OK [0.022 secs, 8600 KB]
> Run 4: OK [0.022 secs, 8600 KB]
> Run 5: OK [0.011 secs, 8600 KB]
> Run 6: OK [0.140 secs, 11636 KB]
> Run 7: OK [0.151 secs, 11372 KB]
> Run 8: OK [0.378 secs, 15116 KB]
> Run 9: OK [0.367 secs, 13972 KB]
> Run 10: OK [0.464 secs, 17840 KB]
> Run 11: OK [0.572 secs, 17924 KB]
> Run 12: OK [0.745 secs, 22880 KB]
> Run 13: OK [0.680 secs, 22620 KB]
【CODE】
/*
ID:jie22221
TASK:cowpol
LANG:C++
*/
#include #include #include #include #define clr(a) memset((a),(0),sizeof(a))
using namespace std;
const int maxn=200005;
struct edge{int y;edge *next;};
int n,k;
int ans[maxn],belong[maxn],d[maxn],mdp[maxn],f[maxn];
bool v[maxn];

struct Link_t{
edge *ls[maxn];
void add(int x,int y){
edge *t=(edge*)malloc(sizeof(edge));
t->y=y; t->next=ls[x]; ls[x]=t;
}
}G,Q;

void init(){
clr(G.ls);
clr(Q.ls);
clr(d);
clr(mdp);
clr(v);
clr(ans);
scanf("%d%d",&n,&k);
for (int x,i=1;i<=n;i++){
scanf("%d%d",&belong[i],&x);
G.add(x,i);
G.add(i,x);
}
}

void dfs(int p,int fa){
for (edge *t=G.ls[p];t;t=t->next)
if (t->y!=fa){
d[t->y]=d[p]+1;
dfs(t->y,p);
}
}

void make_Q(){
for (int i=1;i<=n;i++)
if (!mdp[belong[i]] || d[i]>d[mdp[belong[i]]])
mdp[belong[i]]=i;
for (int i=1;i<=n;i++)
if (mdp[belong[i]]!=i){
Q.add(i,mdp[belong[i]]);
Q.add(mdp[belong[i]],i);
}
}

int gf(int p){
if (f[p]==p) return p;
return f[p]=gf(f[p]);
}

void solve(int p,int fa){
f[p]=p;
for (edge *t=G.ls[p];t;t=t->next)
if (t->y!=fa)
solve(t->y,p);
v[p]=true;
for (edge *t=Q.ls[p];t;t=t->next)
if (v[t->y])
ans[belong[p]]=max(ans[belong[p]],d[t->y]+d[p]-2*d[gf(t->y)]);
f[p]=fa;
}

int main(){
freopen("cowpol.in","r",stdin);
freopen("cowpol.out","w",stdout);
init();
dfs(1,0);
make_Q();
solve(1,0);
for (int i=1;i<=k;i++)
printf("%dn",ans[i]);
}

[USACO2010 Mar Gold 【balloc】]贪心、线段树★★

【题目链接】http://ace.delos.com/ioiupload?init=1&a=uNRSh9JKRlk
【题目】
Farmer John最近新建立了一个农场,并且正在接受奶牛的畜栏分配请求,有些
畜栏会看到农场美妙的风景。:)

农场由N (1 <= N <= 100,000) 个畜栏组成,编号为1..N,畜栏i可以最多容纳C_i只奶牛
(1 <= C_i <= 100,000)。奶牛i希望得到连续的一段畜栏,表示为一段区间 (A_i,B_i) 。
这样的话奶牛可以在这段牛棚里面转悠。(当然,这段畜栏必须要有足够的空间)

给出M (1 <= M <= 100,000) 个请求,请求出不超过畜栏承载量的情况下,最多可以满足的请求数。 考虑下面展示的一个农场: 编号 : 1 2 3 4 5
+—+—+—+—+—+
容量 : | 1 | 3 | 2 | 1 | 3 |
+—+—+—+—+—+
奶牛1 XXXXXXXXXXX (1, 3)
奶牛2 XXXXXXXXXXXXXXX (2, 5)
奶牛3 XXXXXXX (2, 3)
奶牛4 XXXXXXX (4, 5)

FJ 不能够同时满足4头奶牛的请求,否则3,4号畜栏就会有太多的奶牛。

考虑到奶牛2的请求需要一个区间包含3号和4号畜栏,我们尝试这样一种方案,让1,3,4号奶牛
的请求都得到满足,这样没有畜栏超出容量的限制,因此,对于上述情况的答案就是3,三头奶牛
(1,3,4号)的要求可以得到满足。

内存限制: 32MB

时间限制: 2S

文件名: balloc

输入格式:

* 第1行:两个用空格隔开的整数:N和M

* 第2行到N+1行:第i+1行表示一个整数C_i

* 第N+2到N+M+1行: 第i+N+1行表示2个整数 A_i和B_i

样例输入(balloc.in)

5 4
1
3
2
1
3
1 3
2 5
2 3
4 5

输出格式:

* 第一行: 一个整数表示最多能够被满足的要求数

样例输出:(balloc.out)

3
**********************************************************************
【算法分析】
如果我们对于每个牛(l,r)的r按第一关键字增排序,l为第二关键字减排序,那么能填就填,最后能填多少就是答案。

如何证明这个贪心策略呢?
下面是我的证明。。。不知道严不严谨:
首先,假设两牛如果是包含关系,那么被包含的牛一定比包含别人的牛优
假设这个贪心策略不是最优的,那么必然存在这样一种情况:如果填了排序后第i个牛,那么,导致了后面的j牛和k牛都不能取。而不填这第i个牛,则两人都能取。(因为这样,填i牛才是阻碍了后面的牛,导致了非最优解)
不妨设Ri1、Lj>Ri || Lk>Ri 显然不满足由于填了i牛,j牛和k牛都不能取这一题设。
2、Lj<=Li || Lk<=Li 那么,j和k中有一个牛和i是包含关系,最优性显然。
3、Li<=Lj<=Ri && Li<=Lk<=Ri 令p=max(Lj,Lk) 那么因为要让不填i牛,j牛和k牛能同时取,那么[p,Ri]这一段的最小容量必然要>=2。但是,如果满足了这个条件,那么i牛和取得p的那一只牛必然能共存,那么造成了矛盾,所以不可能。
那么,最后综上所述,上面红字的那种情况不存在。所以“这个贪心策略不是最优的”这个假设就不成立。于是,该算法最优性得证。

然后剩下的,就用线段树判断能不能填就成了。
【时间复杂度】O(m lg n + n lg n)
【空间复杂度】O(n + m)
【CODE】
/*
ID:jie22221
TASK:balloc
LANG:C++
*/
#include #include #include #include #define AA (*((Query*)A))
#define BB (*((Query*)B))
using namespace std;
const int MAXN=100005;
int n,m;
int C[MAXN];
struct Node{int l,r,Min,delta;}Tr[MAXN*3];
struct Query{int l,r;}Q[MAXN];

void init(){
cin >> n >> m;
for (int i=1;i<=n;i++) cin >> C[i];
for (int i=1;i<=m;i++)
cin >> Q[i].l >> Q[i].r;
}

int cmp(const void *A,const void *B){
if (AA.r-BB.r) return AA.r-BB.r;
return BB.l-AA.l;
}

void pushdown(int p){
Tr[p].Min-=Tr[p].delta;
if (Tr[p].l!=Tr[p].r){
Tr[p*2].delta+=Tr[p].delta;
Tr[p*2+1].delta+=Tr[p].delta;
}
Tr[p].delta=0;
}

void update(int p){
pushdown(p*2);
pushdown(p*2+1);
Tr[p].Min=min(Tr[p*2].Min,Tr[p*2+1].Min);
}

void build(int p,int l,int r){
Tr[p].l=l; Tr[p].r=r; Tr[p].delta=0;
if (l==r){
Tr[p].Min=C[l];
return;
}
int mid=(l+r) >> 1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
update(p);
}

bool Can(int p,int l,int r){
if (r pushdown(p);
if (l<=Tr[p].l && Tr[p].r<=r) return Tr[p].Min>0;
return Can(p*2,l,r)&&Can(p*2+1,l,r);
}

void Del(int p,int l,int r){
if (r if (l<=Tr[p].l && Tr[p].r<=r){Tr[p].delta++; return;}
pushdown(p);
Del(p*2,l,r);
Del(p*2+1,l,r);
update(p);
}

void solve(){
build(1,1,n);
int ans=0;
for (int i=1;i<=m;i++)
if (Can(1,Q[i].l,Q[i].r)){
ans++;
Del(1,Q[i].l,Q[i].r);
}
cout << ans << endl;
}

int main(){
freopen("balloc.in","r",stdin);
freopen("balloc.out","w",stdout);
ios::sync_with_stdio(false);
init();
qsort(Q+1,m,sizeof(Query),cmp);
solve();
return 0;
}

[USACO2010 Mar Gold 【gather】]树形dp

【题目链接】http://ace.delos.com/ioiupload?init=1&a=uNRSh9JKRlk
【题目大意】
就是给一颗树,树上的点都有不同数目的奶牛。
然后边有边权。
让你选一个结点,使得所有牛到这个点走的路程之和最小。
【算法分析】
随便拿个点做根。
两次DFS。
第一次算出两个信息:
1、以该结点为根的子树上的牛的总数目。
2、该结点的子孙到该点的距离之和。
第二次算出另一个信息:
从父亲来的那部分需要的距离之和。
最后将两部分相加即可。
【其它】1Y。
【CODE】
/*
ID:jie22221
TASK:gather
LANG:C++
*/
#include #include #include #include using namespace std;
typedef long long lld;
const int N=105555;
struct edge{int x,y,w;edge *next;}*ls[N];
int n;
int c[N];
lld f[N],cow[N],From_Fa[N],ans=(lld)(1) << 60; void addedge(int x,int y,int w){
edge *t=(edge *)malloc(sizeof(edge));
t->x=x; t->y=y; t->w=w; t->next=ls[x]; ls[x]=t;
}

void init(){
ios::sync_with_stdio(false);
cin >> n;
for (int i=1;i<=n;i++) cin >> c[i];
for (int i=1,x,y,w;i cin >> x >> y >> w;
addedge(x,y,w);
addedge(y,x,w);
}
}

void predfs(int p,int fa){
cow[p]=c[p];
for (edge *t=ls[p];t;t=t->next)
if (t->y!=fa){
predfs(t->y,p);
f[p]+=cow[t->y]*t->w+f[t->y];
cow[p]+=cow[t->y];
}
}

void dfs(int p,int fa){
for (edge *t=ls[p];t;t=t->next)
if (t->y!=fa){
From_Fa[t->y]=From_Fa[p]+f[p]-f[t->y]-cow[t->y]*t->w+(cow[1]-cow[t->y])*t->w;
dfs(t->y,p);
}
ans=min(ans,From_Fa[p]+f[p]);
}

int main(){
freopen("gather.in","r",stdin);
freopen("gather.out","w",stdout);
init();
predfs(1,0);
dfs(1,0);
cout << ans << endl;
return 0;
}