There is a war解題報告【比賽1008 / HDU2435】
題目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2435
題目大意:有N個城市(編號從1開始),連有一些橋(有向邊),破壞每條橋需要一個代價(有向邊上的權)。
Country N要用最小的代價(下面記爲MIN_COST)破壞掉一些橋,讓Country 1無法到達Country N。
在Country N的摧毀行動前,Country 1可以先選擇一座橋把這座橋變成無法拆除的狀態,或者新建一座無法拆除的橋,不過這兩種橋的端點不能是城市1和城市N。Country 1也可以對橋不作任何改動。
Country 1的目標是,使Country N的MIN_COST最大。
解題思路:首先是最暴力的方法,每次在2..N-1中選擇兩個端點,搭建或者改造原有橋樑(下面稱爲“造無敵橋”),計算最小割。但是這樣顯然是會TLE的。
接下來考慮,造無敵橋的兩個端點必然是分別在最小割的兩端。如果有多個最小割,選擇其中任意一個最小割都可以,這一點我不知道怎麼證明,不過自己畫幾個圖可以想象一下。
那麼優化可以先求一個全局最大流,從源點1出發進行一次DFS,可以得到一個最小割的分割結果INIT_FLOW。不妨記作“源集”和“彙集”,“源集”到“彙集”之間的邊就是最小割,流量等於容量。
造無敵橋的端點一頭在“源集”,一頭在“彙集”,枚舉即可,那麼怎麼計算造橋之後的MIN_COST呢?
其實不需要重新計算一次全局的網絡流。計算INIT_FLOW之後可以得到一張殘餘網絡c,在c上Country 1和Country N已經不聯通了。而加上無敵橋樑(i, j)後,Country1和Country N可能重新聯通。在殘餘網絡c上計算F1=max_flow(1, i), F2=max_flow(j, n),分別是破壞1到i,j到n之間聯通關係所需要的代價,INIT_FLOW+min(F1, F2)就是造無敵橋之後的MIN_COST。而這裏的F1,F2在得到“源集”和“彙集”之後是可以預處理一下存起來方便枚舉的。
源代碼:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=110;
int usr_min(int x, int y){if (x<y) return x; return y;}
int EdmondsKarp(int g[][maxn],int n,int src,int dst,int f[][maxn])
{
int d[maxn]={},p[maxn],r[maxn]={},c[maxn]={n};
int flow=0,delta=INT_MAX,h[maxn],v=src;
memset(f,0,sizeof(*f)*n);
p[src]=-1;
while(d[src]<n)
{
bool flag=true;;
h[v]=delta;
for(int i=r[v];i<n;i++)
if(g[v][i]>f[v][i]&&d[v]==d[i]+1)
{
if(delta>g[v][i]-f[v][i]) delta=g[v][i]-f[v][i];
flag=false;
r[v]=i;p[i]=v;v=i;
break;
}
if(flag)
{
int t=n-1;
for(int i=0;i<n;i++)
if(t>d[i]&&f[v][i]<g[v][i]) {t=d[i];r[v]=i;}
if(--c[d[v]]==0) break;
d[v]=t+1;
c[d[v]]++;
if(p[v]!=-1) {v=p[v];delta=h[v];}
}
else if(v==dst)
{
flow+=delta;
while(p[v]!=-1)
{
f[v][p[v]]-=delta;
f[p[v]][v]+=delta;
v=p[v];
}
delta=INT_MAX;
}
}
return flow;
}
void dfs(int k, int n, bool v[], int c[][maxn]) //找源集合
{
v[k]=true;
for (int i=0; i<n; i++)
{
if (!v[i] && c[k][i])
dfs(i, n, v, c);
}
}
int main()
{
int cs, n, m, a, b, cc;
int init; //初始流
static int f1[maxn], f2[maxn];
int ans, tmp;
static int g[maxn][maxn];
static int f[maxn][maxn];
static int c[maxn][maxn];
static int ff[maxn][maxn];
bool v[maxn];
int src[maxn], dst[maxn];
int n1, n2;
scanf("%d", &cs);
while (cs--)
{
scanf("%d%d", &n, &m);
if (m==0){printf("0\n");continue;}
memset(g, 0, sizeof(g));
for (int i=0; i<m; i++)
{
scanf("%d%d%d", &a, &b, &cc);
g[a-1][b-1]=cc;
}
ans=init=EdmondsKarp(g, n, 0, n-1, f); //最小割
for (int i=0; i<n; i++) //得殘餘網絡
for (int j=0; j<n; j++)
{
c[i][j]=g[i][j]-f[i][j];
}
memset(v, 0, sizeof(v));
dfs(0, n, v, c); //源集
n1=n2=0;
for (int i=1; i<n-1; i++) //源集彙集
if (v[i]) src[n1++]=i;
else dst[n2++]=i;
for (int i=0; i<n1; i++)
f1[i]=EdmondsKarp(c, n, 0, src[i], f);
for (int i=0; i<n2; i++)
f2[i]=EdmondsKarp(c, n, dst[i], n-1, f);
for (int i=0; i<n1; i++)
for (int j=0; j<n2; j++)
{
tmp=usr_min(f1[i], f2[j])+init;
if (tmp>ans) ans=tmp;
}
printf("%d\n", ans);
}
return 0;
}
Collision Detection解題報告【比賽1009 / HDU2436】
題目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=2436
題目大意:
求長方體和球是否相交(相切看做相交)長方體的每條邊和座標軸平行
解題思路:
記圓心爲(x0, y0, z0)。容易想到,計算長方體上離圓心最近一點(x', y', z')到圓心的距離dismin就可以判斷出YES和NO。
由於“長方體的每條邊和座標軸平行”(這個條件非常重要),可以知道長方體上任意一點左邊(x, y, z)滿足xmin<=x<=xmax, y,z相同。而xmin, xmax等可以直接由長方體的八個頂點座標得到。
從dismin^2=(x0-x)^2+(y0-y)^2+(z0-z)^2可以知道,要找到(x', y', z')這一點,其實xyz三個那種歌方向上是完全獨立的,分別在xmin<=x<=xmax, ymin<=y<=ymax, zmin<=z<=zmax中間選取合適的x, y, z使(x0-x)^2、(y0-y)^2和(z0-z)^2都最小就可以了。
根本不需要傳說中的計算幾何啊……
源代碼:
#include <cstdio>
#include <cmath>
using namespace std;
typedef struct {
double x, y, z;
}POINT;
double dis(double x1, double y1, double z1, double x2, double y2, double z2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
int main()
{
int cs;
POINT cube[10], ball;
double r, mindis;
double xmax, ymax, zmax, xmin, ymin, zmin, x, y, z;
scanf("%d", &cs);
while (cs--)
{
for (int i=0; i<8; i++)
scanf("%lf%lf%lf", &cube[i].x, &cube[i].y, &cube[i].z);
scanf("%lf%lf%lf%lf", &ball.x, &ball.y, &ball.z, &r);
xmin=cube[0].x, xmax=cube[0].x;
ymin=cube[0].y, ymax=cube[0].y;
zmin=cube[0].z, zmax=cube[0].z;
for (int i=1; i<8; i++)
{
if (cube[i].x<xmin) xmin=cube[i].x;
if (cube[i].x>xmax) xmax=cube[i].x;
if (cube[i].y<ymin) ymin=cube[i].y;
if (cube[i].y>ymax) ymax=cube[i].y;
if (cube[i].z<zmin) zmin=cube[i].z;
if (cube[i].z>zmax) zmax=cube[i].z;
}
if (ball.x<xmin) x=xmin;
else if (ball.x>xmax) x=xmax;
else x=ball.x;
if (ball.y<ymin) y=ymin;
else if (ball.y>ymax) y=ymax;
else y=ball.y;
if (ball.z<zmin) z=zmin;
else if (ball.z>zmax) z=zmax;
else z=ball.z;
mindis=dis(x, y, z, ball.x, ball.y, ball.z);
if (mindis>r)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
Jerboas解題報告【比賽1010 / HDU2437】
題目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=2437
題目大意:
某種鳥有兩種住所P和T,給出鳥的起點,鳥的終點可以是任何一種P住所。這隻鳥希望到終點時的總路程是K的倍數,問最近滿足要求的住所是哪一個,路徑多少。
解題思路:
感謝CBX測試了一下數據裏頭並沒有重邊,於是直接DFS。用數組a[i][j]=b表示從起點到i點,路程mod K爲j的最短路爲b。這樣可以有一個剪枝。
源代碼:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1010
#define maxm 50000
const int INF=(1<<29);
struct edge {int v,w,next;};
int n, m, s, k;
int res, dst;
int g[maxn];
edge e[maxm];
char str[2048];
bool v[maxn];
int a[maxn][maxn];
void dfs(int now, int len)
{
// printf("visit %d\n", now);
if (str[now]=='P' && len%k==0)
{
if (len<res || (len==res && now<dst)) {res=len; dst=now;}
return;
}
for (int i=g[now]; i!=-1; i=e[i].next)
{
if (a[e[i].v][(len+e[i].w)%k]==-1 || len+e[i].w<a[e[i].v][(len+e[i].w)%k])
{
a[e[i].v][(len+e[i].w)%k]=len+e[i].w;
dfs(e[i].v, len+e[i].w);
}
}
}
int main()
{
int cs, aa, bb, cc;
scanf("%d", &cs);
for (int css=1; css<=cs; css++)
{
//init
scanf("%d%d%d%d", &n, &m, &s, &k);
scanf("%s", str);
memset(g, 255, sizeof(g));
for (int i=0, j=0; i<m; i++)
{
scanf("%d%d%d", &aa, &bb, &cc);
aa--; bb--;
e[j].v=bb; e[j].w=cc; e[j].next=g[aa]; g[aa]=j++;
}
memset(a, 255, sizeof(a));
res=INF;
a[s-1][0]=0;
dfs(s-1, 0);
if (res==INF)
printf("Case %d: -1 -1\n", css);
else
printf("Case %d: %d %d\n", css, res, dst+1);
}
while (1);
return 0;
}
賽後個人總結:
1009:當時直接想到複雜的計算幾何去了。一直到在紙上畫了圖才意識到“長方體的每條邊和座標軸平行”這個重要條件。AC的速度不夠,好像還WA了一次當時考慮不夠周全。以後做計算幾何題的時候還是應該自己多寫幾個數據測一下。
1008:最近幾場多校裏最大流最小割用了很多次。這道題需要對增廣路有比較清晰的認識。這一種加邊的方式和優化的方式確實值得想一想,以後遇上了就沒問題了。寫的時候對最小割不唯一的時候“源集”和“彙集”的劃分方式不是很確定,還好過了。
1010:最早CBX的思路是拓補排序然後利用那個a數組搞,但是我寫了一半的時候發現可能時間上不是很允許,然後想到了DFS。
總體上來講今天過題的準確率不是很高,有5次罰時,以後應該避免。
賽後隊伍總結:
隊伍合作的感覺總體還是應該不錯的,不過策略還有待慢慢磨合,也期待大家碰面以後的訓練。
1、開場第一道題太晚開始寫,對先對哪題發起進攻選擇太慢,這樣對最後的總罰時也會比較不利。個人感覺開場如果沒有直接發現水題應該跟風做第一道題,如果比別人晚20分鐘開始寫其實也相當於一次WA了。
2、做之前思路清晰再開始寫,如果思路不清晰寧願不寫。在有三臺機子的時候,一旦開始寫了以後不要輕易換題,這在時間上也是很不利的。除非是一直WA一直WA調不出來再放棄。
3、以後一起寫程序的時候,拿一張大紙專門記錄每到題的情況,再拿一些紙,看過題的人稍微詳細地寫一下題意。
4、有問題討論,確定能寫的時候其他人可以攻新題。
大概就是這些,這幾次比賽的過題數量都還可以,但是罰時都比較不利,以後應該注意。畢竟賽場上金銀之差往往就是因爲罰時。
加油。