1. 程式人生 > >【FCS NOI2018】福建省冬摸魚筆記 day3

【FCS NOI2018】福建省冬摸魚筆記 day3

tar 這樣的 多少 計算 另一個 abs body targe 指針

第三天。

計算幾何,講師:葉芃(péng)。

dalao們日常不記筆記。@ghostfly233說他都知道了,就盼著自適應辛普森積分。

我計算幾何基礎不好……然而還是沒怎麽講實現,感覺沒聽什麽東西進去。

不過還是記了一些公式。

同時@ghostfly233在後面打農藥。

然後講到了有關圓的東西。講完了,下課了。辛普森有興趣的同學回去自己看看吧。

辛普森:???

@ghostfly233非常難受。


中午劃水。


下午有一題和早上的有關。

T1半平面交+面積,簡單題,我半平面交寫錯了,只有80。

T2神秘圖論結論+神秘數學/神秘DP,那個結論第一天就接觸過了,然而我不知道放那兒有啥用,於是不會做,其實再證一個結論就簡單狀壓DP了。

T3毒瘤分治啥的,沒想法,搞了個最壞\(O(n^2)\)的RMQ,結果70分???@qrc和我差不多,只有30分,因為他預處理\(O(n^2)\),233333。數據出水了。

150分比較舒服。

【T1】

題面:求一個凸包內部,隨機取點P,使得P與給定一邊組成的三角形,比P與其他邊組成的三角形面積都要小,取到這樣的點P的概率。

題解:一看就是半平面交再算面積,算出給定邊和其他邊的那條直線(使得P在直線一側,三角形更小),交一交就好了。

難點在於如何求出直線,其實推推式子就沒問題,第二個是半平面交,我寫掛了,幸好不是什麽嚴重錯誤,於是80分。

#include<cstdio>
#include<cmath>
#include<algorithm>
#define db double
using namespace std;
const db eps=1e-10;
struct vec{db x,y;vec(db x=0,db y=0):x(x),y(y){}};
inline vec operator+(vec x,vec y){return vec(x.x+y.x,x.y+y.y);}
inline vec operator-(vec x,vec y){return vec(x.x-y.x,x.y-y.y);}
inline vec operator*(db  x,vec y){return vec(x*y.x,x*y.y);}
inline vec operator*(vec x,db  y){return vec(y*x.x,y*x.y);}
inline db operator *(vec x,vec y){return x.x*y.y-x.y*y.x;}
inline db mod(vec x){return sqrt(x.x*x.x+x.y*x.y);}
inline bool zero(db x){if(x<=eps&x>=-eps)return 1;return 0;}
struct line{vec p,v;line(vec p=vec(0,0),vec v=vec(0,0)):p(p),v(v){}};
inline bool cmp(line x,line y){return atan2(x.v.y,-x.v.x)<atan2(y.v.y,-y.v.x);}
void print(vec x){printf("(%lf, %lf)",x.x,x.y);}
void print(line x){print(x.p);printf(" -> ");print(x.v);puts("");}
inline vec jiao(line x,line y){
	return x.p+((((y.p-x.p)*y.v)/(x.v*y.v))*x.v);
}
inline line clac(line x,line y){
	if(zero(x.v*y.v)) return line(y.p+((x.p-y.p)*(mod(x.v)/(mod(x.v)+mod(y.v)))),x.v-y.v);
	return line(jiao(x,y),x.v-y.v);
}
inline bool left(line x,vec y){
	return (x.v*(y-x.p))>-eps;
}
int n,cnt; db ans1,ans2;
vec dots[100001];
line edge[100001];
line edg2[200001];
line que[200001];int l=1,r=0;
int main(){
	freopen("convex.in","r",stdin);
	freopen("convex.out","w",stdout);
/*	while(1){
		line p1,p2;
		line v;
		scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&p1.p.x,&p1.p.y,&p1.v.x,&p1.v.y,&p2.p.x,&p2.p.y,&p2.v.x,&p2.v.y);
		print(p1); print(p2);
		v=clac(p1,p2);
		print(v);
	}*/
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lf%lf",&dots[i].x,&dots[i].y);
//======
	ans1+=dots[n]*dots[1];
	for(int i=1;i<n;++i) ans1+=dots[i]*dots[i+1];
	ans1/=2; ans1=abs(ans1);
//======
	for(int i=1;i<n;++i) edge[i]=line(dots[i],dots[i+1]-dots[i]);
	edge[n]=line(dots[n],dots[1]-dots[n]);
	edg2[++cnt]=line(dots[1],dots[n]-dots[1]);
	for(int i=2;i<=n;++i) edg2[++cnt]=line(dots[i],dots[i-1]-dots[i]);
	for(int i=2;i<=n;++i) edg2[++cnt]=clac(edge[1],edge[i]);
	sort(edg2+1,edg2+cnt+1,cmp);
//======
//	puts("======");
//	for(int i=1;i<=cnt;++i) print(edg2[i]);
//	puts("======");
//======
	que[++r]=edg2[1]; que[++r]=edg2[2];
	for(int i=3;i<=cnt;++i){
//		puts("######");
//		for(int j=l;j<=r;++j) print(que[j]);
//		puts("######");
		while(l<=r-1&&left(edg2[i],jiao(que[r],que[r-1]))) --r;
		while(l<=r-1&&left(edg2[i],jiao(que[l],que[l+1]))) ++l;
		if(zero(que[r].v*edg2[i].v)){
			if(l<=r-1&&left(edg2[i],jiao(que[r],que[r-1]))) --r;
			else continue;
		}
		que[++r]=edg2[i];
	}
	while(l<=r-1&&left(edg2[l],jiao(que[r],que[r-1]))) --r;
//======
//	puts("======");
//	for(int i=l;i<=r;++i) print(que[i]);
//	puts("======");
//======
	if(l==r-1) ans2=0;
	else{
		ans2+=jiao(que[l],que[l+1])*jiao(que[l],que[r]);
		ans2+=jiao(que[r],que[l])*jiao(que[r],que[r-1]);
		for(int i=l+1;i<=r-1;++i) ans2+=jiao(que[i],que[i+1])*jiao(que[i],que[i-1]);
		ans2/=2; ans2=abs(ans2);
	}
//======
	printf("%.4f",ans2/ans1);
	return 0;
}

【T2】

題意:給定一個二分圖G={V=X+Y,E},每個點有價值,求有多少個V的子集S的價值大於等於給定的t,並且S可以被原圖G中的一個匹配覆蓋。\(|X|,|Y|\leq 20\)

題解:Hall定理:對於一個二分圖G={X+Y,E},那麽存在一個能覆蓋X的匹配當且僅當對於任意一個X的子集,從它引邊到Y後,Y被覆蓋的點的數量大於等於這個子集的點的數量。

那麽,還要證明一個結論:對於二分圖G={X+Y,E},如果X的子集P能被一個G的匹配MP覆蓋,Y的子集Q能被一個G的匹配MQ覆蓋,那麽G必有一個匹配能覆蓋P+Q。

為什麽呢?如果P中的一個點通過MP到達Y的點不在Q中,那這條邊就可以選取,因為P沒有另一個點通過MP能到達同一個點;反之,如果在Q中,那這個點再通過MQ到達X,重復這個過程,最後會形成一條鏈或一個環,在鏈中的話我們隔著取邊,不論長度為奇數或偶數都可以取到,如果是一個環,那麽必須是一個偶環,因為是二分圖,所以我們仍然隔著取邊就好了。

那麽我們要做的是:①處理X,Y中的每個子集是否有匹配能覆蓋它,②把可以的按照價值排序,③雙指針掃一遍。

①可以用狀壓DP解決。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,t,s,num[1048576],A[1048576],B[1048576],vA[1048576],vB[1048576],sA[1048576],sB[1048576],cA,cB;
bool fA[1048576],fB[1048576];
long long ans;
int main(){
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	scanf("%d%d",&n,&m); s=n>m?n:m;
	for(int i=1;i<1<<s;++i) num[i]=num[i-(i&-i)]+1;
	char ch;
	for(int i=0;i<n;++i) for(int j=0;j<m;++j)
		ch=getchar(), ch==‘1‘?A[1<<i]|=1<<j,B[1<<j]|=1<<i:ch!=‘0‘?--j:0;
	for(int i=0;i<1<<n;++i) A[i]=A[i&-i]|A[i-(i&-i)];
	for(int i=0;i<1<<m;++i) B[i]=B[i&-i]|B[i-(i&-i)];
	for(int i=0;i<n;++i) scanf("%d",vA+(1<<i));
	for(int i=0;i<m;++i) scanf("%d",vB+(1<<i));
	for(int i=0;i<1<<n;++i) vA[i]=vA[i&-i]+vA[i-(i&-i)];
	for(int i=0;i<1<<m;++i) vB[i]=vB[i&-i]+vB[i-(i&-i)];
	for(int i=0;i<1<<n;++i){
		if(num[A[i]]<num[i]) continue;
		fA[i]=1;
		for(int j=0;j<n;++j) if(i>>j&1)
			fA[i]&=fA[i^1<<j];
		fA[i]?sA[cA++]=vA[i]:0;
	} sort(sA,sA+cA);
	for(int i=0;i<1<<m;++i){
		if(num[B[i]]<num[i]) continue;
		fB[i]=1;
		for(int j=0;j<n;++j) if(i>>j&1)
			fB[i]&=fB[i^1<<j];
		fB[i]?sB[cB++]=vB[i]:0;
	} sort(sB,sB+cB);
	scanf("%d",&t);
	for(int i=0,j=cB;i<cA;++i){
		while(j>0&&sA[i]+sB[j-1]>=t) --j;
		ans+=cB-j;
	}
	printf("%lld",ans);
	return 0;
}

【FCS NOI2018】福建省冬摸魚筆記 day3