North American Invitational Programming Contest 2018

A. Cut it Out!

枚举第一刀,那么之后每切一刀都会将原问题划分成两个子问题。

考虑DP,设$f[l][r]$表示$l$点顺时针一直到$r$点还未切割的最小代价,预处理出每条边的代价转移即可。

时间复杂度$O(n^3)$。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=422;
const double eps=1e-8;
const double inf=1e100;
inline int sgn(double x){
	if(x>eps)return 1;
	if(x<-eps)return -1;
	return 0;
}
inline void up(double&a,double b){if(a>b)a=b;}
int n,m,i,j,k;
double ans=inf,f[N][N];
bool v[N][N];
struct P{
	double x,y;
	P(){}
	P(double _x,double _y){x=_x,y=_y;}
	P operator-(const P&b)const{return P(x-b.x,y-b.y);}
	P operator+(const P&b)const{return P(x+b.x,y+b.y);}
	P operator*(const double&b)const{return P(x*b,y*b);}
	double len(){return hypot(x,y);}
	double len2(){return x*x+y*y;}
	void read(){scanf("%lf%lf",&x,&y);}
}a[N],b[N];
double wl[N],wr[N];
inline double cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;}
inline double line_intersection(const P&a,const P&b,const P&p,const P&q){
	double U=cross(p-a,q-p),D=cross(b-a,q-p);
	return U/D;
	//return a+(b-a)*(U/D);
}
inline void pre(double&A,double&B,int k){
	A=-inf,B=inf;
	for(int i=0;i<n;i++){
		double now=line_intersection(b[k],b[k+1],a[i],a[i+1]);
		if(now<0.5&&now>A)A=now;
		if(now>0.5&&now<B)B=now;
	}
}
inline double cal(int k,int L,int R){
	k%=m;
	k+=m;
	k%=m;
	if(L>-100){
		L%=m;
		L+=m;
		L%=m;
	}
	if(R>-100){
		R%=m;
		R+=m;
		R%=m;
	}
	double A=wl[k],B=wr[k];
	if(L>=0){
		double now=line_intersection(b[k],b[k+1],b[L],b[L+1]);
		//printf("L=%d %.10f\n",L,now);
		if(now<0.5&&now>A)A=now;
		if(now>0.5&&now<B)B=now;
	}
	if(R>=0){
		double now=line_intersection(b[k],b[k+1],b[R],b[R+1]);
		//printf("R=%d %.10f\n",R,now);
		if(now<0.5&&now>A)A=now;
		if(now>0.5&&now<B)B=now;
	}
	//printf("! %.10f\n",(B-A)*((b[k]-b[k+1]).len()));
	return (B-A-1)*((b[k]-b[k+1]).len());
}
double dfs(int l,int r){//point a[l] -> a[r] are not cut
	if(l>=r)return 0;
	if(v[l][r])return f[l][r];
	double ret=inf;
	for(int i=l;i<r;i++){
		//printf("i=%d cal=%.10f\n",i,cal(i,l-1,r));
		up(ret,dfs(l,i)+dfs(i+1,r)+cal(i,l-1,r));
	}
	v[l][r]=1;
	//printf("f[%d][%d]=%.10f\n",l,r,f[l][r]);
	return f[l][r]=ret;
}
int main(){
	scanf("%d",&n);
	for(i=0;i<n;i++)a[i].read();
	a[n]=a[0];
	scanf("%d",&m);
	for(i=0;i<m;i++)b[i].read();
	b[m]=b[0];
	//cal(6,5,8);
	//dfs(6,8);
	for(i=0;i<m;i++)pre(wl[i],wr[i],i);
	for(i=0;i<m;i++)up(ans,dfs(i+1,i+m)+cal(i,-100,-100));
	for(i=0;i<m;i++)ans+=(b[i]-b[i+1]).len();
	printf("%.15f",ans);
}
/*
4
-100 -100
-100 100
100 100
100 -100
8
-1 -2
-2 -1
-2 1
-1 2
1 2
2 1
2 -1
1 -2
*/

  

B. Double Clique

一个方案合法当且仅当团点数$\times ($团点数$-1)+$独立集度数和$=$团度数和,且存在可行方案满足团是度数最大的若干个点。

找到可行方案后,要么是团里出去一个点,要么是独立集里出去一个点,要么两边各出去一个点。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200010;
int n,m,i,j,x,y,d[N],s[N];ll ans;
int main(){
	scanf("%d%d",&n,&m);
	while(m--)scanf("%d%d",&x,&y),d[x]++,d[y]++;
	sort(d+1,d+n+1);
	reverse(d+1,d+n+1);
	for(i=1;i<=n;i++)s[i]=s[i-1]+d[i];
	for(i=0;i<=n;i++)if(1LL*i*(i-1)+s[n]-s[i]==s[i]){ans=1;break;}
	if(!ans)return puts("0"),0;
	for(j=1;j<=i;j++)if(1LL*(i-1)*(i-2)+s[n]-s[i]+d[j]==s[i]-d[j])ans++;
	for(j=i+1;j<=n;j++)if(1LL*(i+1)*i+s[n]-s[i]-d[j]==s[i]+d[j])ans++;
	for(x=0,j=1;j<=i;j++)if(d[j]==d[i])x++;
	for(y=0,j=i+1;j<=n;j++)if(d[j]==d[i])y++;
	ans+=1LL*x*y;
	printf("%lld",ans%1000000007);
}

  

C. Flashing Fluorescents

注意到答案不超过$n$,枚举答案$ans$,则任何一个可行方案可以由若干个长度互不相等且不超过$ans$的区间异或得到。

设$f[ans][S]$表示长度不超过$ans$能否异或出$S$,枚举当前长度的区间位置转移即可。

时间复杂度$O(2^nn^2)$。

#include<cstdio>
#include<cstring>
int n,i,j,now,S;
bool f[50][1<<16];
char a[50];
int main(){
	scanf("%s",a);
	n=strlen(a);
	for(i=0;i<n;i++)if(a[i]=='0')S^=1<<i;
	f[0][S]=1;
	while(!f[now][0]){
		for(S=0;S<1<<n;S++)f[now+1][S]=f[now][S];
		for(i=0;i<n;i++){
			int mask=0;
			for(j=0;j<now+1&&i+j<n;j++)mask|=1<<(i+j);
			for(S=0;S<1<<n;S++)if(f[now][S])f[now+1][S^mask]=1;
		}
		now++;
	}
	printf("%d",now);
}

  

D. Missing Gnomes

按题意模拟。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() {  }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
int a[N], ans[N];
bool use[N];
int main()
{
	while(~scanf("%d%d", &n, &m))
	{
		for(int i = 1; i <= n; ++i)use[i] = 0;
		
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d", &a[i]);
			use[a[i]] = 1;
		}
		
		int x = 1;
		int g = 0;
		for(int i = 1; i <= m; ++i)
		{
			while(x < a[i])
			{
				if(!use[x])ans[++g] = x;
				++x;
			}
			ans[++g] = a[i];
		}
		while(x <= n)
		{
			if(!use[x])ans[++g] = x;
			++x;
		}
		for(int i = 1; i <= n; ++i)printf("%d\n", ans[i]);
	}
	return 0;
}

/*
【trick&&吐槽】


【题意】


【分析】


【时间复杂度&&优化】


*/

  

E. Prefix Free Code

建立Trie将字符串映射为数字,从前往后枚举LCP,那么这一位能填的数要小于某个值,且前面没出现过,可以用树状数组加速统计,后面能填的数可以用组合数计算。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<cstring>
const int N=1000010,P=1000000007;
int n,m,len,i,j,x,son[N][26],v[N],tot,dfn,a[N],cnt;
int bit[N],fac[N],inv[N],ans;
char s[N];
void dfs(int x){
	if(v[x])v[x]=++dfn;
	for(int i=0;i<26;i++)if(son[x][i])dfs(son[x][i]);
}
inline void ins(int x,int p){for(;x<=n;x+=x&-x)bit[x]+=p;}
inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=bit[x];return t;}
inline int A(int n,int m){return n<m?0:1LL*fac[n]*inv[n-m]%P;}
int main(){
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%s",s);
		len=strlen(s);
		for(x=j=0;j<len;j++){
			if(!son[x][s[j]-'a'])son[x][s[j]-'a']=++tot;
			x=son[x][s[j]-'a'];
		}
		v[x]=1;
	}
	dfs(0);
	for(fac[0]=i=1;i<=n;i++)fac[i]=1LL*fac[i-1]*i%P;
	for(inv[0]=inv[1]=1,i=2;i<=n;i++)inv[i]=1LL*(P-inv[P%i])*(P/i)%P;
	for(i=2;i<=n;i++)inv[i]=1LL*inv[i-1]*inv[i]%P;
	scanf("%s",s);
	for(x=i=0;s[i];i++){
		x=son[x][s[i]-'a'];
		if(v[x])a[++cnt]=v[x],x=0;
	}
	ans=1;
	for(i=1;i<=n;i++)ins(i,1);
	for(i=1;i<=cnt;i++){
		ins(a[i],-1);
		ans=(1LL*A(n-i,m-i)*ask(a[i])+ans)%P;
	}
	printf("%d",ans);
}

  

F. Probe Droids

即求斜率第$k$小的坐标,首先特判掉斜率$=0$或者斜率不存在的情况。

在Stern-Brocot Tree上枚举互质数对作为斜率,然后用类欧几里得算法在$O(\log n)$的时间内统计出直线下方的点数,以此来判断往左走还是往右走。

考虑二分往左往右走的拐点位置,则每次询问的复杂度降低至$O(\log^3n)$。

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<iostream>
using namespace std;
typedef long long ll;
ll ansx,ansy;
ll cal(ll a,ll b,ll c,ll n){
  if(!a||n<0)return 0;
  ll x,y;
  if(a>=c||b>=c){
    x=cal(a%c,b%c,c,n);
    y=a/c*(n*(n+1)/2)+b/c*(n+1)+x;
    return y;
  }
  ll m=(a*n+b)/c;
  x=cal(c,c-b-1,a,m-1);
  y=n*m-x;
  return y;
}
ll calbelow(ll up,ll down,ll n,ll m){
  ll lim=min(n*down/up,m);
  return cal(up,0,down,lim)+(m-lim)*n;
}
ll calexact(ll up,ll down,ll n,ll m){
  return min(n/up,m/down);
}
int check(ll up,ll down,ll n,ll m,ll k){
  if(up>n||down>m)return 2;
  ll below=calbelow(up,down,n,m);
  ll exact=calexact(up,down,n,m);
  //cout<<up<<" "<<down<<" "<<below<<" "<<exact<<endl;
  //[below-exact+1,below]
  if(k>below)return -1;//too small
  if(k<below-exact+1)return 1;//too big
  return 0;
}
void solve(ll n,ll m,ll k){
  ll lu=0,ld=1,ru=1,rd=0,mu,md;
  ll A,B;
  while(1){
    mu=lu+ru;
    md=ld+rd;
    int ret=check(mu,md,n,m,k);
    if(ret==0){
      A=mu,B=md;
      break;
    }
    ll l=1,r=n,fin=0;
    if(ret<0){
      while(l<=r){
        ll mid=(l+r)>>1;
        if(check(lu+ru*mid,ld+rd*mid,n,m,k)<0)l=(fin=mid)+1;else r=mid-1;
      }
      lu+=ru*fin,ld+=rd*fin;
    }else{
      while(l<=r){
        ll mid=(l+r)>>1;
        if(check(ru+lu*mid,rd+ld*mid,n,m,k)==1)l=(fin=mid)+1;else r=mid-1;
      }
      ru+=lu*fin,rd+=ld*fin;
    }
  }
  ll below=calbelow(A,B,n,m);
  ll exact=calexact(A,B,n,m);
  below=below-exact;
  k-=below;
  ansx=B*k;
  ansy=A*k;
  //cout<<A<<"/"<<B<<endl;
}
int main(){
   ll n,m,q,k;
  cin>>n>>m>>q;
  while(q--){
    cin>>k;
    if(k<m){
      cout<<"1 "<<k+1<<endl;
      continue;
    }
    if(k>n*m-n){
      cout<<k-(n*m-n)+1<<" 1"<<endl;
      continue;
    }
    solve(n-1,m-1,k-(m-1));
    cout<<ansy+1<<" "<<ansx+1<<endl;
  }
}

  

G. Rainbow Graph

若只有一个限制,满足拟阵。

对于两个限制,则是两个拟阵的交。

首先特判全部都无解的情况,并将全集$E$作为$k=m$的解。

设$M_1$为限制$1$的拟阵,一个方案合法当且仅当在限制$1$下连通,同理定义$M_2$为限制$2$的拟阵。

建立有向图,原图每条边作为一个点,并添加源汇$S$和$T$。

对于上一个$k$的一组最优解$E$中的某条边$x$,如果去掉它后仍然满足$M_1$,则由$S$向$x$连边;若去掉它后仍然满足$M_2$,则由$x$向$T$连边。

对于$E$中某条边$x$和不在$E$中的某条边$y$,若将$x$换成$y$后满足$M_2$,则由$x$向$y$连边;若满足$M_1$,则由$y$向$x$连边。

用SPFA求出$S$到$T$的最短路,就能得到边数恰好减少$1$的最优解。

时间复杂度$O(n^4)$。

#include<cstdio>
const int N=105,M=100000,inf=~0U>>1;
int n,m,i,S,T,x,y,g[N],v[N<<1],nxt[N<<1],ed,vis[N];
int cost[N],col[N],use[N],ans,fin[N];char ch[9];
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
void dfs(int x,char ban){
  if(vis[x])return;
  vis[x]=1;
  for(int i=g[x];i;i=nxt[i])if(use[i>>1]&&col[i>>1]!=ban)dfs(v[i],ban);
}
inline bool check(char ban){
  int i;
  for(i=1;i<=n;i++)vis[i]=0;
  dfs(1,ban);
  for(i=1;i<=n;i++)if(!vis[i])return 0;
  return 1;
}
namespace Matroid{
int g[N],v[M],nxt[M],ed,q[M],h,t,d[N],pre[N],w[N];bool in[N];
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
inline void ext(int x,int y,int z){
  if(d[x]<=y)return;
  d[x]=y;
  pre[x]=z;
  if(in[x])return;
  q[++t]=x;
  in[x]=1;
}
inline bool find(){
  int i,j;
  S=m+1,T=m+2;
  for(ed=0,i=1;i<=T;i++)g[i]=0;
  for(i=1;i<=m;i++)if(use[i]){
    w[i]=-cost[i];
    use[i]^=1;
    if(check('R'))add(S,i);
    if(check('B'))add(i,T);
    use[i]^=1;
  }else w[i]=cost[i];
  for(i=1;i<=m;i++)if(use[i])for(j=1;j<=m;j++)if(!use[j]){
    use[i]^=1,use[j]^=1;
    if(check('B'))add(i,j);
    if(check('R'))add(j,i);
    use[i]^=1,use[j]^=1;
  }
  for(i=1;i<=T;i++)d[i]=inf,in[i]=0;
  q[h=t=1]=S;
  d[S]=0,in[S]=1;
  while(h<=t){
    x=q[h++];
    //printf("! %d %d %d\n",x,d[x],pre[x]);
    for(i=g[x];i;i=nxt[i])ext(v[i],d[x]+w[v[i]],x);
    in[x]=0;
  }
  if(d[T]==inf)return 0;
  ans+=d[T];
  while(pre[T]!=S)use[T=pre[T]]^=1;
  return 1;
}
}
int main(){
  scanf("%d%d",&n,&m);
  for(ed=i=1;i<=m;i++){
    scanf("%d%d%d%s",&x,&y,&cost[i],ch);
    col[i]=ch[0];
    add(x,y),add(y,x);
    use[i]=1;
    ans+=cost[i];
  }
  if(!check('R')||!check('B')){
    for(i=1;i<=m;i++)puts("-1");
    return 0;
  }
  fin[m]=ans;
  for(i=m-1;i;i--)if(Matroid::find())fin[i]=ans;
  else{
    for(x=1;x<=i;x++)fin[x]=-1;
    break;
  }
  for(i=1;i<=m;i++)printf("%d\n",fin[i]);
}

  

H. Recovery

将所有位置都设成$1$,然后贪心配对行列使得满足条件。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() {  }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
char a[55], b[55];
int aa[55], bb[55];
int ga, gb;
char s[55][55];
int va[55], vb[55];
bool rand(char a[])
{
	int n = random() % 4 + 1;
	for(int i = 0; i < n; ++i)a[i] = rand() % 2 + '0';
	a[n] = 0;
	return 1;
}

char tt[55][55];
char t[55][55];
bool FLAG;
void BF()
{
	FLAG = 0;
	int w = n * m;
	int top = 1 << w;;
	int ansone = -1;
	for(int i = 0; i < top; ++i)
	{
		for(int j = 0; j < w; ++j)
		{
			tt[n - 1 - j / m][m - 1 - j % m] = '0' + (i >> j & 1);
		}
		MS(va, 0);
		MS(vb, 0);
		int one = 0;
		for(int j = 0; j < n; ++j)
		{
			for(int k = 0; k < m; ++k)
			{
				va[j] += tt[j][k] % 2;
				vb[k] += tt[j][k] % 2;
				one += tt[j][k] % 2;
			}
		}
		bool flag = 1;
		for(int j = 0; j < n; ++j)if(va[j] % 2 != a[j] % 2)
		{
			flag = 0;
			break;
		}
		for(int j = 0; j < m; ++j)if(vb[j] % 2 != b[j] % 2)
		{
			flag = 0;
			break;
		}
		if(flag)
		{
			FLAG = 1;
			if(one > ansone)
			{
				ansone = one;
				memcpy(t, tt, sizeof(tt));
			}
		}
	}
}

int main()
{
	while(~scanf("%s%s", a, b))
	//while(rand(a), rand(b))
	{
		n = strlen(a);
		m = strlen(b);
		//puts("input"); puts(a); puts(b);
		
		MS(s, 0);
		for(int i = 0; i < n; ++i)
		{
			for(int j = 0; j < m; ++j)
			{
				s[i][j] = '1';
			}
		}
		ga = gb = 0;
		for(int i = 0; i < n; ++i)
		{
			if(m % 2 != a[i] % 2)
			{
				aa[++ga] = i;
			}
		}
		for(int i = 0; i < m; ++i)
		{
			if(n % 2 != b[i] % 2)
			{
				bb[++gb] = i;
			}
		}
		
		//BF();
		if(ga + gb & 1)
		{
			puts("-1");
			/*
			if(FLAG)
			{
				puts("NO flag");
				while(1);
			}
			*/
		}
		else
		{
			/*
			if(!FLAG)
			{
				puts("NO !flag");
				while(1);
			}
			*/
			
			//printf("ga|gb = %d %d\n", ga, gb);
			
			while(ga < gb)aa[++ga] = 0;
			while(gb < ga)bb[++gb] = 0;
			int g = ga;
			
			/*
			int g = min(ga, gb);
			for(int i = g + 1; i <= ga; ++i)
			{
				bb[++gb] = 0;
			}
			for(int i = g + 1; i <= gb; ++i)
			{
				aa[++ga] = 0;
			}
			*/
			
			sort(aa + 1, aa + ga + 1);
			sort(bb + 1, bb + gb + 1);
			/*printf("ga|gb = %d %d\n", ga, gb);
			for(int i = 1; i <= ga; ++i)printf("%d ", aa[i]); puts("");
			for(int i = 1; i <= gb; ++i)printf("%d ", bb[i]); puts("");
			*/
			g = max(ga, gb);
			
			for(int i = 1; i <= g; ++i)
			{
				s[aa[i]][bb[i]] = '0';
			}
			
			for(int i = 0; i < n; ++i)puts(s[i]);
			
			/*
			for(int i = 0; i < n; ++i)
			{
				for(int j = 0; j < m; ++j)
				{
					if(s[i][j] != t[i][j])
					{
						puts("s[i][j] != t[i][j]");
						
						for(int i = 0; i < n; ++i)puts(s[i]);
						for(int i = 0; i < n; ++i)puts(t[i]);
						while(1);
					}
				}
			}
			*/
			
			/*
			MS(va, 0);
			MS(vb, 0);
			for(int i = 0; i < n; ++i)
			{
				for(int j = 0; j < m; ++j)
				{
					va[i] += s[i][j] % 2;
					vb[j] += s[i][j] % 2;
				}
			}
			for(int i = 0; i < n; ++i)
			{
				if(va[i] % 2 != a[i] % 2)
				{
					puts("NO A");
					while(1);
				}
			}
			for(int i = 0; i < m; ++i)
			{
				if(vb[i] % 2 != b[i] % 2)
				{
					puts("NO B");
					while(1);
				}
			}
			*/
		}
	}
	return 0;
}

/*
【trick&&吐槽】


【题意】


【分析】


【时间复杂度&&优化】


*/

  

I. Red Black Tree

设$f[i][j]$表示考虑$i$的子树,里面选了$j$个红点的方案数,转移时只枚举有效的$j$即可得到$O(nm)$的时间复杂度。

#include<cstdio>
const int N=200010,M=1005,P=1000000007;
int n,m,i,x,g[N],nxt[N],size[N],vip[N];
int f[N][M],h[M];
void dfs(int x){
	size[x]=vip[x];
	//case 1 not choose x
	f[x][0]=1;
	for(int y=g[x];y;y=nxt[y]){
		dfs(y);
		for(int j=0;j<=size[x]+size[y]&&j<=m;j++)h[j]=0;
		for(int j=0;j<=size[y]&&j<=m;j++)for(int k=0;k<=size[x]&&j+k<=m;k++)
			h[j+k]=(1LL*f[y][j]*f[x][k]+h[j+k])%P;
		size[x]+=size[y];
		for(int j=0;j<=size[x]+size[y]&&j<=m;j++)f[x][j]=h[j];
	}
	//case 2 choose x
	f[x][vip[x]]=(f[x][vip[x]]+1)%P;
}
int main(){
	scanf("%d%d",&n,&m);
	for(i=2;i<=n;i++){
		scanf("%d",&x);
		nxt[i]=g[x];g[x]=i;
	}
	for(i=1;i<=m;i++)scanf("%d",&x),vip[x]=1;
	dfs(1);
	for(i=0;i<=m;i++)printf("%d\n",f[1][i]);
}

  

J. Winter Festival

因为所有简单环边权和都要是奇数,因此当两个简单环有公共边时不可能满足条件,所以当且仅当图是仙人掌的时候才有解。

设$f[i][j][x][y]$表示考虑DFS生成树中$i$的子树,$i$与$i$父亲的边的边权为$j$,$i$与$i$父亲的边所在环的边权和模$2$为$x$,$i$与$i$父亲的边所在环的非树边边权为$y$的最小代价。

转移时需要通过另一个辅助数组$h[S][j][x][y]$来进行不同子树的合并,其中$j,x,y$含义与$f$相同,而$S$表示$x$点周围存在的边权集合。

时间复杂度$O(n+m)$。

#include<cstdio>
#include<cstdlib>
#define rep(i,n) for(int i=0;i<n;i++)
using namespace std;
const int N=100010,inf=100000000;
int n,m,i,x,y,g[N],v[N<<1],nxt[N<<1],ed;
int mark[N],fa[N],vis[N],dfn;
int f[N][3][2][3];
int dp[1<<3][2][3],h[1<<3][2][3];
int istop[N],isbot[N];
int ban[3][1<<3];
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
inline void up(int&a,int b){a>b?(a=b):0;}
inline void clr(){
	rep(S,8)rep(j,2)rep(k,3)h[S][j][k]=inf;
}
inline void go(){
	rep(S,8)rep(j,2)rep(k,3)dp[S][j][k]=h[S][j][k];
}
void dfs(int x,int y){
	fa[x]=y;
	vis[x]=++dfn;
	for(int i=g[x];i;i=nxt[i]){
		int u=v[i];
		if(u==y)continue;
		if(!vis[u]){
			dfs(u,x);
		}else if(vis[u]<vis[x]){
			int j=x;
			isbot[x]=1;
			while(j!=u){
				mark[j]++;
				if(mark[j]>1){
					puts("-1");
					exit(0);
				}
				if(fa[j]==u)istop[j]=1;
				j=fa[j];
			}
		}
	}
	rep(S,8)rep(j,2)rep(k,3)dp[S][j][k]=inf;
	if(!y)dp[0][0][0]=0;
	else{
		//add the cycle edge
		if(isbot[x])rep(c,3)up(dp[1<<c][c&1][c],c);
		else dp[0][0][0]=0;
	}
	for(int i=g[x];i;i=nxt[i]){
		int u=v[i];
		if(u==y)continue;
		if(fa[u]==x){
			clr();
			if(istop[u]){
				rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
					rep(A,3)if(!ban[A][S])rep(B,2)rep(C,3)if(f[u][A][B][C]<inf){
						if(B!=1)continue;
						if(ban[C][S])continue;
						if((A+C)%3==1)continue;
						up(h[S|(1<<A)|(1<<C)][j][k],dp[S][j][k]+f[u][A][B][C]);
					}
			}else if(mark[u]){
				rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
					rep(A,3)if(!ban[A][S])rep(B,2)rep(C,3)if(f[u][A][B][C]<inf){
						up(h[S|(1<<A)][(j+B)&1][C],dp[S][j][k]+f[u][A][B][C]);
					}
			}else{
				rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
					rep(A,3)if(!ban[A][S])rep(B,1)rep(C,1)if(f[u][A][B][C]<inf){
						up(h[S|(1<<A)][j][k],dp[S][j][k]+f[u][A][B][C]);
					}
			}
			go();
		}
	}
	rep(S,3)rep(j,2)rep(k,3)f[x][S][j][k]=inf;
	if(y){
		//add the father edge
		if(mark[x]){
			rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
				rep(c,3)if(!ban[c][S])up(f[x][c][(j+c)&1][k],dp[S][j][k]+c);
		}else{
			rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
				rep(c,3)if(!ban[c][S])up(f[x][c][j][k],dp[S][j][k]+c);
		}
	}else{
		rep(S,8)rep(j,2)rep(k,3)if(dp[S][j][k]<inf)
			up(f[x][0][0][0],dp[S][j][k]);
	}
}
int main(){
	rep(i,3)rep(j,8)rep(k,3)if((j>>k&1)&&(i+k)%3==1)ban[i][j]=1;
	scanf("%d%d",&n,&m);
	for(ed=i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
	int ans=0;
	for(i=1;i<=n;i++)if(!vis[i]){
		dfs(i,0);
		if(f[i][0][0][0]>=inf){
			puts("-1");
			exit(0);
		}
		ans+=f[i][0][0][0];
	}
	printf("%d",ans);
}

  

K. Zoning Houses

若不删除任何点,则答案为区间$x$坐标的极差与$y$坐标极差的较大值。

若删除一个点,则最优方案下一定是删除$x$或者$y$坐标最小或者最大的$4$个点之一,线段树维护即可。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=100010,M=262150,inf=1000000010;
int n,m,i,x,y,ans;
P xmi[M],xma[M],ymi[M],yma[M];
void build(int x,int a,int b){
	if(a==b){
		scanf("%d%d",&xmi[x].first,&ymi[x].first);
		xmi[x].second=ymi[x].second=a;
		xma[x]=xmi[x];
		yma[x]=ymi[x];
		return;
	}
	int mid=(a+b)>>1;
	build(x<<1,a,mid),build(x<<1|1,mid+1,b);
	xmi[x]=min(xmi[x<<1],xmi[x<<1|1]);
	xma[x]=max(xma[x<<1],xma[x<<1|1]);
	ymi[x]=min(ymi[x<<1],ymi[x<<1|1]);
	yma[x]=max(yma[x<<1],yma[x<<1|1]);
}
P askxmi(int x,int a,int b,int c,int d){
	if(c>d)return P(inf,0);
	if(c<=a&&b<=d)return xmi[x];
	int mid=(a+b)>>1;
	P t(inf,0);
	if(c<=mid)t=askxmi(x<<1,a,mid,c,d);
	if(d>mid)t=min(t,askxmi(x<<1|1,mid+1,b,c,d));
	return t;
}
P askymi(int x,int a,int b,int c,int d){
	if(c>d)return P(inf,0);
	if(c<=a&&b<=d)return ymi[x];
	int mid=(a+b)>>1;
	P t(inf,0);
	if(c<=mid)t=askymi(x<<1,a,mid,c,d);
	if(d>mid)t=min(t,askymi(x<<1|1,mid+1,b,c,d));
	return t;
}
P askxma(int x,int a,int b,int c,int d){
	if(c>d)return P(-inf,0);
	if(c<=a&&b<=d)return xma[x];
	int mid=(a+b)>>1;
	P t(-inf,0);
	if(c<=mid)t=askxma(x<<1,a,mid,c,d);
	if(d>mid)t=max(t,askxma(x<<1|1,mid+1,b,c,d));
	return t;
}
P askyma(int x,int a,int b,int c,int d){
	if(c>d)return P(-inf,0);
	if(c<=a&&b<=d)return yma[x];
	int mid=(a+b)>>1;
	P t(-inf,0);
	if(c<=mid)t=askyma(x<<1,a,mid,c,d);
	if(d>mid)t=max(t,askyma(x<<1|1,mid+1,b,c,d));
	return t;
}
inline int cal(int x,int y,int z){
	return max(
	max(askxma(1,1,n,x,z-1).first,askxma(1,1,n,z+1,y).first)-min(askxmi(1,1,n,x,z-1).first,askxmi(1,1,n,z+1,y).first)
	,
	max(askyma(1,1,n,x,z-1).first,askyma(1,1,n,z+1,y).first)-min(askymi(1,1,n,x,z-1).first,askymi(1,1,n,z+1,y).first)
	);
}
int main(){
	scanf("%d%d",&n,&m);
	build(1,1,n);
	while(m--){
		scanf("%d%d",&x,&y);
		ans=cal(x,y,askxmi(1,1,n,x,y).second);
		ans=min(ans,cal(x,y,askxma(1,1,n,x,y).second));
		ans=min(ans,cal(x,y,askymi(1,1,n,x,y).second));
		ans=min(ans,cal(x,y,askyma(1,1,n,x,y).second));
		printf("%d\n",ans);
	}
}

  

posted @ 2018-04-07 02:34  Claris  阅读(2061)  评论(2编辑  收藏  举报