D1T1 魚
首先的做法是顯然的:直接枚舉即可。但是顯然枚舉量太大了。
這類題有一種思路,就是考慮哪些點是比較關鍵的,固定比較關鍵的點。
這題中,顯然D和A是最關鍵的。因此固定D,把其他點繞D極角排序,並按順序枚舉A。
B、C的方案數:
顯然BCAD,且BC中點在AD上。
因此事先把每條線段的方向、中點hash並存下來,查找時lower_bound一下即可。
E、F的方案數:
過D做DHAD於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 多邊形
~~氣死我了,如果我沒看錯題目,我就當場切掉了。。。~~以後再看錯題我就女裝
通過觀察樣例可以發現一個狀態是終止狀態,當且僅當多邊形內所有邊都連向節點。
對於每個不包含節點的三角形,需要通過旋轉操作把它幹掉(即變成包含節點的三角形)。
例如下圖,第一種方案依次幹掉標號爲3,2,4的三角形,第二種方案依次幹掉標號爲3,4,2的三角形。
實際上,我們可以把每個不包含節點的三角形放在一棵二叉森林上。
多邊形 | => | 樹 |
---|---|---|
=> | ||
=> |
在二叉森林上,我們一次恰好可以幹掉一個三角形,但是父親節點必須在兒子節點之前被幹掉。
因此第一問答案就是二叉森林上節點個數,第二問就是幹掉所有節點的方案數。
考慮用dp求出第二問答案。
其中是把兩個長度爲的序列合併的方案數。這個dp方程並不難理解。
最終答案就是
其中
是把長度分別爲的序列合併的方案數。
考慮簡化這個結果。上述dp方程可以改寫成
因此
考慮如何在一次操作後更改這個答案。
-
當更改的節點爲根節點時,更改後被刪除。
此時
-
當更改的節點不爲根節點時,如圖所示:
此時會轉換爲下面的圖形:
此時
#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做法:把滿足條件的二元組放入隊列,並不斷更新即可。
考慮如何優化邊數。
設兩端節點標記相同的邊爲藍色邊,兩端節點標記不同的邊爲紅色邊。
則滿足條件的路徑經過邊的顏色也是迴文的。
對於路徑上邊顏色相同的一段,顯然它在邊顏色相同的一個連通塊中。
這一段可以通過來回走一條邊而長度+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;
}