C - Toll Management IV
我要气死了!下次我不自己看输入输出我是狗!对于树边的$a_i$,考虑非树边对他的贡献,从小到大将非树边$(u,v)$排序,他能贡献的是(u->lca)和(v->lca),缩树或者是树链剖分都可以。对于非树边的$b_i$,倍增求最大即可。
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> #include<map> using namespace std; typedef long long ll; typedef pair<int,int>P; const int maxn=1e5+5; int n, m, a[maxn], par[maxn], id[maxn], b[maxn]; int p[maxn][18], mx[maxn][18]; vector<P>g[maxn]; struct node{ int from, to, cost, id; node(int from=0, int to=0, int cost=0, int id=0):from(from),to(to),cost(cost),id(id){} bool operator<(const node& g)const{ return cost<g.cost; } }edge[maxn], edge1[maxn]; int T; bool f[maxn]; int fd(int x){return x==par[x]?x:par[x]=fd(par[x]);} void unite(int x, int y){ x=fd(x); y=fd(y); if(x==y) return; par[x]=y; } int fat[maxn], depth[maxn]; void dfs(int u, int fa, int d){ fat[u]=fa; depth[u]=d; for(int i=0;i<g[u].size();i++){ int v=g[u][i].first; if(v==fa) continue; dfs(v,u,d+1); id[v]=g[u][i].second; mx[v][0]=edge[g[u][i].second].cost; } } void init(){ for(int i=1;i<=n;i++) if(fat[i]!=i) p[i][0]=fat[i]; for(int j=1;j<=15;j++){ for(int i=1;i<=n;i++){ if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1], mx[i][j]=max(mx[i][j-1],mx[p[i][j-1]][j-1]); } } } int query(int u, int v){ if(depth[u]<depth[v]) swap(u,v); int t=depth[u]-depth[v]; int ans=0; for(int i=0;i<=15;i++) if(t&(1<<i)) ans=max(ans,mx[u][i]), u=p[u][i]; if(u==v) return ans; for(int i=15;i>=0;i--){ if(p[u][i]!=p[v][i]) ans=max(ans,mx[u][i]), ans=max(ans,mx[v][i]), u=p[u][i], v=p[v][i]; } ans=max(ans,mx[u][0]); ans=max(ans,mx[v][0]); return ans; } int main(){ scanf("%d",&T); int cas=0; while(T--){ scanf("%d%d",&n,&m); int u, v, w; for(int i=1;i<=n;i++) g[i].clear(), par[i]=i; for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&w), edge[i]=node(u,v,w,i), a[i]=b[i]=-1; for(int i=1;i<=n-1;i++){ u=edge[i].from; v=edge[i].to; w=edge[i].cost; g[u].push_back(P(v,i)); g[v].push_back(P(u,i)); } memset(p,-1,sizeof(p)); memset(mx,0,sizeof(mx)); dfs(1,1,1); init(); sort(edge+n,edge+m+1); for(int i=n;i<=m;i++){ u=edge[i].from; v=edge[i].to; w=edge[i].cost; int j=edge[i].id; b[j]=w-query(u,v); u=fd(u); v=fd(v); int tu=u, tv=v; while(u!=v){ if(depth[u]>depth[v]) u=fd(fat[u]); else v=fd(fat[v]); } int t=u; u=tu, v=tv; while(u!=t) a[id[u]]=w-edge[id[u]].cost, unite(u,t), u=fd(fat[u]); while(v!=t) a[id[v]]=w-edge[id[v]].cost, unite(v,t), v=fd(fat[v]); } ll ans=0; for(int i=1;i<=m;i++){ int k=edge[i].id; if(k<n) ans+=1LL*k*a[k]-1LL*k*k; else ans+=1LL*k*k*b[k]-k; } printf("Case %d: %lld\n",++cas,ans); } return 0; }
F - Unique Party
暴力。首先,我们可以进行一些常规操作,压缩矩形,即枚举矩形的上下界,然后再考虑左右边界。对于一次询问,实际上我们可以将矩阵变换一下,将大于或等于$h$的位置替换成$1$,将小于$h$位置替换为$-1$,然后问题就变成了求最大的子矩阵使得矩阵的和不小于零,然后加上之前的枚举上下界,实际上,就变成了一个一维问题:给定一个整数序列,求最长的子区间,使得区间和不小于零。对于此问题,我们考虑枚举左边界,注意都这没有单调性,故我们不能尺取来优化,但是如果用一些数据结构维护的话,可能会超时,所以这个问题我们必须做到线性复杂度。实际上,虽然每个左端点的最优的右端点是无单调性的,但是,假如左端点$l$对应的最优右端点为$r$,那么对于$l+1$来讲,它的右端点不能小于$r+1$,因此,如果小的话,答案只会更差,所以说,实际上我们的右端点还有可以选择不断右移的,但是存在一个问题,我们如何快速判断当前的右端点是否是最优解呢?我们可以假设$r$不是当前$l$的最优解,那么从$r+1$开始,必然存在连续的一段子区间,它的和加上$[l,r]$的和不小于零,因此,我们不妨对每个点维护一个$suf[i]$,表示从$i$位置往后,包括$i$的最大连续子段和,显然,如果$r$不是最优解,那么$[l,r]$区间的和加上$suf[r+1]$是不小于零的,而$suf[i]$可以线性求,有了$suf[i]$,我们前一个问题也可以线性求,所以这个问题就变成了线性时间复杂度了。最后加上询问次数以及枚举上下界,复杂度为$O(qn^3)$。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 int T,R,C; 5 const int N=260; 6 int a[N][N],b[N],suf[N]; 7 int main() { 8 scanf("%d",&T); 9 int kase=0,q; 10 while(T--) { 11 scanf("%d%d",&R,&C); 12 for(int i=1;i<=R;i++) for(int j=1;j<=C;j++) scanf("%d",a[i]+j); 13 scanf("%d",&q); 14 printf("Case %d:\n",++kase); 15 while(q--) { 16 int h,ans=0;; 17 scanf("%d",&h); 18 for(int l=1;l<=R;l++) { 19 for(int j=1;j<=C;j++) b[j]=0; 20 for(int r=l;r<=R;r++) { 21 for(int j=1;j<=C;j++) { 22 if(a[r][j]>=h) b[j]++; 23 else b[j]--; 24 } 25 suf[C+1]=0; 26 for(int i=C;i;i--) { 27 suf[i]=b[i]; 28 suf[i]+=std::max(0,suf[i+1]); 29 } 30 int p=0,now=0;; 31 for(int i=1;i<=C;i++) { 32 p=std::max(p,i-1); 33 if(p<i) now=0; 34 while(p<C&&now+suf[p+1]>=0) { 35 p++; 36 now+=b[p]; 37 } 38 if(now>=0) ans=std::max(ans,(r-l+1)*(p-i+1)); 39 if(p>=i) now-=b[i]; 40 } 41 } 42 } 43 printf("%d\n",ans); 44 } 45 } 46 return 0; 47 }
H - Design New Capital
$NTT$。首先观察到,点不会在坐标轴上,$(0,0)$能成为最优点的条件就是,$x$轴上方的点的个数等于下方,$y$轴左边的点数等于右边,从而得到第一象限的点的个数和第三象限相等,第二和第四相等。不妨对一三象限和二四象限分开考虑,设第$i$象限的点数为$f[i]$,那么取$k$个点的方案数$ans[k]=\sum_{i=0}^{\frac{k}{2}}C(f[1],i)C(f[3],i)C(f[2],k-i)C(f[4],k-i)$,当$k$为奇数时显然方案数为$0$,偶数的话,根据式子可以发现,它实际上是个卷积,所以$FFT$优化一下,题目比较友好,给了个$NTT$模数,且打表可以发现,原根为$3$,所以可以使用$NTT$。
1 #include<iostream> 2 #include<cstdio> 3 const int N=1e5+5; 4 typedef long long ll; 5 const ll mod=7340033,ot=3; 6 int T; 7 ll qpow(ll x,ll y) { 8 ll res=1; 9 x%=mod;y%=(mod-1); 10 if(y<0) y+=mod-1; 11 for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod; 12 return res; 13 } 14 ll p[N],inv[N]; 15 void init() { 16 p[0]=1; 17 for(int i=1;i<N;i++) p[i]=p[i-1]*i%mod; 18 inv[N-1]=qpow(p[N-1],mod-2); 19 for(int i=N-2;~i;i--) inv[i]=inv[i+1]*(i+1)%mod; 20 } 21 ll C(int n,int m) { 22 if(n<m) return 0; 23 return p[n]*inv[m]%mod*inv[n-m]%mod; 24 } 25 void NTT(ll *a,int n,bool inv=0) { 26 for(int i=0,j=0;i<n;i++) { 27 if(j>i) std::swap(a[i],a[j]); 28 int k=n; 29 while(j&(k>>=1)) j&=~k; 30 j|=k; 31 } 32 ll p=inv?-(mod-1)/2:(mod-1)/2; 33 for(int i=1;i<n;i<<=1) { 34 ll c=qpow(ot,p/i),o=1; 35 for(int k=0;k<i;k++,o=o*c%mod) { 36 for(int j=k;j<n;j+=i<<1) { 37 int e=j+i; 38 ll t=o*a[e]%mod; 39 a[e]=(a[j]-t+mod)%mod; 40 a[j]=(a[j]+t)%mod; 41 } 42 } 43 } 44 if(inv) { 45 p=qpow(n,mod-2); 46 for(int i=0;i<n;i++) a[i]=a[i]*p%mod; 47 } 48 } 49 void mul(ll *a,ll *b,int la,int lb) { 50 int n=1;while(n<la+lb-1) n<<=1; 51 for(int i=la;i<n;i++) a[i]=0; 52 for(int i=lb;i<n;i++) b[i]=0; 53 NTT(a,n);NTT(b,n); 54 for(int i=0;i<n;i++) a[i]=a[i]*b[i]%mod; 55 NTT(a,n,true); 56 } 57 ll a[2][N<<3]; 58 int num[4],n; 59 int main() { 60 scanf("%d",&T); 61 init();int kase=0; 62 while(T--) { 63 scanf("%d",&n); 64 for(int j=0;j<4;j++) num[j]=0; 65 for(int i=0,x,y;i<n;i++) { 66 scanf("%d%d",&x,&y); 67 if(x>0&&y>0) num[0]++; 68 else if(x<0&&y>0) num[1]++; 69 else if(x<0&&y<0) num[2]++; 70 else num[3]++; 71 } 72 int m[2]={0,0}; 73 for(int i=0;i<2;i++) { 74 int up=std::min(num[i],num[i+2]); 75 for(int j=0;j<=up+up;j++) a[i][j]=0; 76 m[i]=up+up+1; 77 for(int j=0;j<=up;j++) a[i][j+j]=C(num[i],j)*C(num[i+2],j)%mod; 78 } 79 mul(a[0],a[1],m[0],m[1]); 80 printf("Case %d:\n",++kase); 81 for(int i=1;i<=n;i++) printf("%lld%c",a[0][i]," \n"[i==n]); 82 } 83 return 0; 84 }
I - Numbered Cards
$dp$。首先,我们使用$bitmask$来表示每个数位是否出现过,如果$mask$第$i$位不为0,它就出现过。然后我们用$f[mask]$表示数位出现情况为$mask$的数字有多少个,这个可以通过数位$dp$求解。然后我们再用$dp[mask]$表示数位出现情况为$mask$的选取数的方案数,显然,如果我们只选一个数,那么就有方案数$f[mask]$,否则的话,我们考虑最小的那个数位有多少种选择,也就是考虑我们选出的包含最小数位的那个数还包含哪些数位,假设它的数位情况为$s$,则有$dp[mask]=f[s]\times dp[s \oplus mask]$,我们枚举所有情况转移即可,复杂度$O(3^{10})$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 const int N=11,M=1<<10; 5 int dp[N][M]; 6 typedef long long ll; 7 ll f[M]; 8 const ll mod=1e9+7; 9 int now,a[N],num[M],top; 10 int dfs(int u,int mask,bool limit,bool zero) { 11 if(u<0) return now==mask?1:0; 12 if(!limit&&!zero&&dp[u][mask]!=-1) return dp[u][mask]; 13 int ans=0; 14 int up=limit?a[u]:9; 15 for(int i=0;i<=up;i++) { 16 int to=mask; 17 if(i) to|=1<<i; 18 else if(!zero) to|=1; 19 if((to|now)==now) ans+=dfs(u-1,to,limit&&i==up,zero&&!i); 20 } 21 if(!limit&&!zero) dp[u][mask]=ans; 22 return ans; 23 } 24 void solve(int n) { 25 top=0; 26 while(n) { 27 a[top++]=n%10; 28 n/=10; 29 } 30 } 31 int main() { 32 int kase=0,T; 33 scanf("%d",&T); 34 while(T--) { 35 int n; 36 scanf("%d",&n); 37 for(int i=0;i<M;i++) f[i]=0,num[i]=0; 38 solve(n); 39 ll ans=0; 40 for(int i=2;i<M;i++) { 41 now=i; 42 memset(dp,-1,sizeof(dp)); 43 num[i]=dfs(top-1,0,true,true); 44 f[i]=num[i]; 45 int lowbit=i&-i; 46 for(int s=(i-1)&i;s;s=(s-1)&i) { 47 if(s<2) continue; 48 if((s^i)<2) continue; 49 if(!(s&lowbit)) continue; 50 f[i]=(f[i]+num[s]*f[s^i])%mod; 51 } 52 ans=(ans+f[i])%mod; 53 54 } 55 printf("Case %d: %lld\n",++kase,ans); 56 } 57 return 0; 58 }