• 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
    ",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
    ",R,now);
    		if(now<0.5&&now>A)A=now;
    		if(now>0.5&&now<B)B=now;
    	}
    	//printf("! %.10f
    ",(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
    ",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
    ",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

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

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

    时间复杂度$O(nlog 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
    ", ans[i]);
    	}
    	return 0;
    }
    
    /*
    【trick&&吐槽】
    
    
    【题意】
    
    
    【分析】
    
    
    【时间复杂度&&优化】
    
    
    */
    

      

    E. Prefix Free Code

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

    时间复杂度$O(nlog 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
    ",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
    ",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
    ", 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
    ", 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
    ",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(nlog 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
    ",ans);
    	}
    }
    

      

  • 相关阅读:
    Webpack2 那些路径
    Nginx alias 和 root配置
    前端代码监控
    Class和构造函数的异同
    Async和await
    如何在git中删除指定的文件和目录
    微信小程序数字转化条形码和二维码
    vue 结合swiper插件实现广告公告上下滚动的效果
    vue2.0 结合better-scroll 实现下拉加载
    FormData对象提交表单和form提交表单
  • 原文地址:https://www.cnblogs.com/clrs97/p/8730429.html
Copyright © 2020-2023  润新知