North American Invitational Programming Contest 2018
A. Cut it Out!
枚举第一刀,那么之后每切一刀都会将原问题划分成两个子问题。
考虑DP,设f[l][r]表示l点顺时针一直到r点还未切割的最小代价,预处理出每条边的代价转移即可。
时间复杂度O(n^3)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #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
按题意模拟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | #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,然后贪心配对行列使得满足条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | #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)的时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | #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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #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); } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· 手把手教你更优雅的享受 DeepSeek
· AI工具推荐:领先的开源 AI 代码助手——Continue
· 探秘Transformer系列之(2)---总体架构
· V-Control:一个基于 .NET MAUI 的开箱即用的UI组件库
· 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现
2017-04-07 BZOJ4083 : [Wf2014]Wire Crossing
2017-04-07 BZOJ4081 : [Wf2014]Skiing