HNOI2019選講

D1T1 魚

傳送門
Fish

首先O(N6)O(N^6)的做法是顯然的:直接枚舉即可。但是顯然枚舉量太大了。

這類題有一種思路,就是考慮哪些點是比較關鍵的,固定比較關鍵的點。

這題中,顯然D和A是最關鍵的。因此固定D,把其他點繞D極角排序,並按順序枚舉A。

B、C的方案數:

顯然BC\perpAD,且BC中點在AD上。

因此事先把每條線段的方向、中點hash並存下來,查找時lower_bound一下即可。

E、F的方案數:

過D做DH\perpAD於D,則E、F都在DH右側。

因此旋轉A時,不斷增加、刪除在DH右側的點,把它們到D的距離用map存下即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,t,tot,l,r,sum,delta;
#define ll long long
ll gcd(ll a,ll b){if(b)return gcd(b,a%b);return a;}
ll ans;map<ll,int>mp;
struct point{ll x,y;}p[N],a[N<<1],dir,lp,rp;
bool  operator< (point a,point b){if(a.x==b.x)return a.y<b.y;return a.x<b.x;}
bool  operator==(point a,point b){return a.x==b.x&&a.y==b.y;}
point operator+ (point a,point b){return (point){a.x+b.x,a.y+b.y};}
point operator- (point a,point b){return (point){a.x-b.x,a.y-b.y};}
ll    operator* (point a,point b){return a.x*b.y-a.y*b.x;}
ll    dot       (point a,point b){return a.x*b.x+a.y*b.y;}
ll len(point a){return a.x*a.x+a.y*a.y;}
bool where(point a){return a.y>0||(a.y==0&&a.x>0);}
bool cmp(point a,point b){
	if(where(a)==where(b))return b*a>0;
	return where(a)<where(b);
}
point norm(point a){
	if(!where(a)){a.x=-a.x;a.y=-a.y;}
	static ll g;g=gcd(a.x,a.y);
	a.x/=g;a.y/=g;
	return a;
}
struct line{point dir,mid;}lin[N*N];
bool operator<(line a,line b){
	if(a.dir==b.dir){
		if(dot(a.dir,a.mid)==dot(b.dir,b.mid))return a.mid<b.mid;
		return dot(a.dir,a.mid)<dot(b.dir,b.mid);
	}
	return a.dir<b.dir;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&p[i].x,&p[i].y);
	for(int i=1;i<=n;i++)for(int j=1;j<i;j++)
		lin[++tot]=(line){norm(p[j]-p[i]),p[i]+p[j]};
	sort(lin+1,lin+tot+1);
	for(int i=1;i<=n;i++){
		t=0;
		for(int j=1;j<=n;j++)if(j!=i)a[++t]=p[j]-p[i];
		sort(a+1,a+t+1,cmp);
		for(int j=1;j<=t;j++)a[j+t]=a[j];
		l=1;r=0;sum=0;
		mp.clear();
		for(int j=1;j<=t;j++){
			while(r<t<<1&&(a[r+1]*a[j]>0||(a[r+1]*a[j]==0&&r+1<n)\
				||dot(a[j],a[r+1])<0))sum+=mp[len(a[++r])]++;
			while(l<=r&&dot(a[j],a[l])>=0)sum-=--mp[len(a[l++])];
			dir=norm((point){a[j].y,-a[j].x});
			lp=p[i]+p[i]   ;
			rp=a[j]+a[j]+lp;
			if(rp<lp)swap(lp,rp);
			delta=lower_bound(lin+1,lin+tot+1,(line){dir,rp})-upper_bound(lin+1,lin+tot+1,(line){dir,lp});
			ans+=(ll)sum*delta;
		}
	}
	printf("%lld",ans<<2);
	return 0;
}

D1T3 多邊形

傳送門

~~氣死我了,如果我沒看錯題目,我就當場切掉了。。。~~以後再看錯題我就女裝

通過觀察樣例可以發現一個狀態是終止狀態,當且僅當多邊形內所有邊都連向節點nn

對於每個不包含節點nn的三角形,需要通過旋轉操作把它幹掉(即變成包含節點nn的三角形)。

例如下圖,第一種方案依次幹掉標號爲3,2,4的三角形,第二種方案依次幹掉標號爲3,4,2的三角形。

Figure 0

實際上,我們可以把每個不包含節點nn的三角形放在一棵二叉森林上。

多邊形 =>
Figure 1 => Figure 2
Figure 3 => Figure 4

在二叉森林上,我們一次恰好可以幹掉一個三角形,但是父親節點必須在兒子節點之前被幹掉。

因此第一問答案就是二叉森林上節點個數,第二問就是幹掉所有節點的方案數。

考慮用dp求出第二問答案。
dpv=dplc×dprc×(szlc+szrcszlc) dp_v=dp_{lc}\times dp_{rc}\times{sz_{lc}+sz_{rc}\choose sz_{lc}}
其中(szlc+szrcszlc)sz_{lc}+sz_{rc}\choose sz_{lc}是把兩個長度爲szlc,szrcsz_{lc},sz_{rc}的序列合併的方案數。這個dp方程並不難理解。

最終答案就是
dproot×(szrootszroot1,szroot2,) \prod{dp_{root}}\times{\sum sz_{root}\choose sz_{root1},sz_{root2},\cdots}

其中
(szrootszroot1,szroot2,)=(szroot)!(szroot!) {\sum sz_{root}\choose sz_{root1},sz_{root2},\cdots}=\frac{(\sum sz_{root})!}{\prod(sz_{root}!)}

是把長度分別爲szroot1,szroot2,sz_{root1},sz_{root2},\cdots的序列合併的方案數。

考慮簡化這個結果。上述dp方程可以改寫成
dpv=dplc×dprc×(szlc+szrc)!szlc!szrc!dpv=dplc×dprc×szv!szvszlc!szrc!dpvszv!=dplcszlc!dprcszrc!szv dp_v=dp_{lc}\times dp_{rc}\times\frac{(sz_{lc}+sz_{rc})!}{sz_{lc}!sz_{rc}!}\\ dp_v=dp_{lc}\times dp_{rc}\times\frac{\frac{sz_v!}{sz_v}}{sz_{lc}!sz_{rc}!}\\ \frac{dp_v}{sz_v!}=\frac{\frac{dp_{lc}}{sz_{lc}!}\frac{dp_{rc}}{sz_{rc}!}}{sz_v}
因此
ans2=dproot×(szroot)!(szroot!)=dprootszroot!(szroot)!=(szroot)!szv=ans1!szv ans2=\prod dp_{root}\times\frac{(\sum sz_{root})!}{\prod(sz_{root}!)}\\ =\prod\frac{dp_{root}}{sz_{root}!}(\sum sz_{root})!\\ =\frac{(\sum sz_{root})!}{\prod sz_v}\\ =\frac{ans1!}{\prod sz_v}

考慮如何在一次操作後更改這個答案。

  1. 當更改的節點vv爲根節點時,更改後vv被刪除。

    此時
    ans1=ans11ans2=ans2×szvszans1 ans1&#x27;=ans1-1\\ ans2&#x27;=ans2\times\frac{sz_v}{sz_{ans1}}

  2. 當更改的節點vv不爲根節點時,如圖所示:
    Figure 5

    此時會轉換爲下面的圖形:
    Figure 6

    此時
    ans1=ansans2=ans2×szv1+sz2+sz3 ans1&#x27;=ans\\ ans2&#x27;=ans2\times\frac{sz_v}{1+sz_2+sz_3}

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=100005;
const ll p=1000000007;
int AKHNOI,n,m,l[N],r[N],ans1,c[N][2],fa[N],sz[N];
ll inv[N],fac[N],ans2=1;
set<int>S[N];
map<pair<int,int>,int>mp;
void _build(int v,int u){
  int y=0,x;
  for(set<int>::iterator it=S[u].lower_bound(v);it!=S[u].end();++it){
  	x=*it;
  	if(y){
  		_build(y,x);
  		l[x]=y;
  		r[x]=u;
  	}
  	y=x;
  }
}
int dfs(int x,int y){
  if(y==x+1)return 0;
  int v=mp[make_pair(x,y)];
  c[v][0]=dfs(x,v);
  c[v][1]=dfs(v,y);
  sz[v]=1+sz[c[v][0]]+sz[c[v][1]];
  fa[c[v][0]]=v;
  fa[c[v][1]]=v;
  ans2=ans2*inv[sz[v]]%p;
  return v;
}
void build(){
  inv[1]=1;
  fac[1]=1;
  for(int i=2;i<=n;i++){
  	inv[i]=(p-p/i)*inv[p%i]%p;
  	fac[i]=fac[i-1]*i%p;
  }
  S[n].insert(1);
  for(int i=2;i<=n;i++)S[i].insert(i-1);
  for(int i=0;i<n-3;i++){
  	int x,y;
  	scanf("%d%d",&x,&y);
  	if(x>y)swap(x,y);
  	S[y].insert(x);
  }
  _build(1,n);
  for(int i=2;i<n;i++)mp[make_pair(l[i],r[i])]=i;
  ans1=n-2;
  for(int i=2;i<n;i++)if(r[i]==n){
  	--ans1;
  	dfs(l[i],i);
  }
  ans2=ans2*fac[ans1]%p;
}
int main(){
  int x,y,v,f,a1;ll a2;
  scanf("%d%d",&AKHNOI,&n);
  build();
  if(AKHNOI)printf("%d %lld\n",ans1,ans2);
  else printf("%d\n",ans1);
  scanf("%d",&m);
  while(m--){
  	scanf("%d%d",&x,&y);
  	if(x>y)swap(x,y);
  	v=mp[make_pair(x,y)];
  	if(fa[v]){
  		f=(v==c[fa[v]][0]);
  		a1=ans1;
  		a2=1+sz[c[v][f]]+sz[c[fa[v]][f]];
  	}else{
  		a1=ans1-1;
  		a2=ans1;
  	}
  	if(AKHNOI)printf("%d %lld\n",a1,ans2*(sz[v]*inv[a2]%p)%p);
  	else printf("%d\n",a1);
  }
  return 0;
}

D2T1 校園旅行

傳送門

30pts做法:把滿足條件的二元組放入隊列,並不斷更新即可。O(n2+m2)O(n^2+m^2)

考慮如何優化邊數。

設兩端節點標記相同的邊爲藍色邊,兩端節點標記不同的邊爲紅色邊。

則滿足條件的路徑經過邊的顏色也是迴文的。

對於路徑上邊顏色相同的一段,顯然它在邊顏色相同的一個連通塊中。

這一段可以通過來回走一條邊而長度+2。因此我們只關心邊顏色相同的連通塊中能走出路徑長度的奇偶性。

因此,如果邊顏色相同的連通塊是二分圖,那麼保留任意一棵生成樹即可。

否則保留任意一棵生成樹,並在任意一個點上加一個自環。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define I inline
#define R register int
namespace io{
    const int l=1<<19;
    char buf[l],*s,*t,c;
    char gc(){
        if(s==t){
            t=(s=buf)+fread(buf,1,l,stdin);
            return s==t?EOF:*s++;
        }
        return *s++;
    }
    template<class IT>void gi(IT &x){
        x=0;c=gc();while(c<'0'||c>'9')c=gc();
        while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    }
};
using io::gi;
const int N=5005,M=500005;
int n,m,q,f[N],vis[N],fa[N],l[2][M],r[2][M],t[2],fg[N],dp[N][N],qu[N*N][2],qh,qt;
char c[N];
vector<int>et[N],e[N];
void dfs(int v,int s){
	fa[v]=s;
	for(R i=0;i<et[v].size();i++){
		int u=et[v][i];
		if(!vis[u]){
			e[v].push_back(u);
			e[u].push_back(v);
			vis[u]=vis[v]^3;
			dfs(u,s);
		}
	}
}
I void addedge(int flag){
	static int f,v,u;
	for(R i=1;i<=n;i++)vis[i]=0;
	for(R i=1;i<=n;i++)et[i].clear();
	for(R i=1;i<=t[flag];i++){
		v=l[flag][i];u=r[flag][i];
		et[v].push_back(u);
		et[u].push_back(v);
	}
	for(R i=1;i<=n;i++)if(!vis[i]){
		vis[i]=1; 
		dfs(i,i);
	}
	for(R i=1;i<=t[flag];i++){
		v=l[flag][i];u=r[flag][i];
		if(vis[v]==vis[u])fg[fa[v]]=1;
	}
}
int main(){
	scanf("%d%d%d%s",&n,&m,&q,c+1);
	for(R i=1;i<=n;i++)f[i]=c[i]^48;
	int v,u,v1,u1,g;
	while(m--){
		scanf("%d%d",&v,&u);
		g=f[v]^f[u];
		++t[g];
		l[g][t[g]]=v;
		r[g][t[g]]=u;
	}
	addedge(0);
	addedge(1);
	for(R i=1;i<=n;i++)if(fg[i])e[i].push_back(i);
	for(R i=1;i<=n;i++){
		dp[i][i]=1;
		++qt;
		qu[qt][0]=i;
		qu[qt][1]=i;
	}
	for(R i=1;i<=n;i++)for(R j=0;j<e[i].size();j++){
		u=e[i][j];
		if(f[i]==f[u]){
			dp[i][u]=1;
			++qt;
			qu[qt][0]=i;
			qu[qt][1]=u;
		}
	}
	while(qh<=qt){
		v=qu[qh][0];u=qu[qh++][1];
		for(int i=0;i<e[v].size();i++)for(int j=0;j<e[u].size();j++){
			v1=e[v][i];u1=e[u][j];
			if(f[v1]==f[u1]&&!dp[v1][u1]){
				dp[v1][u1]=1;
				qu[++qt][0]=v1;qu[qt][1]=u1;
			}
		}
	}
	while(q--){
		scanf("%d%d",&v,&u);
		if(dp[v][u])puts("YES");
		else puts("NO");
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章