SDU暑期集训排位(7)

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
",++cas,ans);
    }
    return 0;
}
View Code

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:
",++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
",ans);
44         }
45     }
46     return 0;
47 }
View Code

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:
",++kase);
81         for(int i=1;i<=n;i++) printf("%lld%c",a[0][i]," 
"[i==n]);
82     }
83     return 0;
84 }
View Code

I - Numbered Cards

$dp$。首先,我们使用$bitmask$来表示每个数位是否出现过,如果$mask$第$i$位不为0,它就出现过。然后我们用$f[mask]$表示数位出现情况为$mask$的数字有多少个,这个可以通过数位$dp$求解。然后我们再用$dp[mask]$表示数位出现情况为$mask$的选取数的方案数,显然,如果我们只选一个数,那么就有方案数$f[mask]$,否则的话,我们考虑最小的那个数位有多少种选择,也就是考虑我们选出的包含最小数位的那个数还包含哪些数位,假设它的数位情况为$s$,则有$dp[mask]=f[s] imes 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
",++kase,ans);
56     }
57     return 0;
58 }
View Code
原文地址:https://www.cnblogs.com/Onlymyheart/p/11373144.html