【多校训练】2021HDU多校3

【前言】
手速场,后面的题都不可做,试图翻盘但是太菜了,连显然的结论都没看出来。
然后我们罚时又爆炸了,于是GG。
rk90,校6/9

1001.Bookshop

不会写,大概是剖分以后询问离线,事实上对应的dfs序上 log ⁡ n \log n logn个区间,然后预处理一堆东西,维护平衡树树支持插入删除减去 k k k,其中减去的时候是一个经典的势能分析。

1002. Destinations

前面的转化不会,问题可以转化为给定若干条熟练,选择总收益最大的一些树链使得两两没有公共点。一个简单的解法是考虑它的对偶问题:在树上选择尽量少的点,每个点可以重复选择多次,满足每条树链选择的点数至少为其收益值,这个可以贪心解决:从深到浅dfs,考虑 x x x时,预处理所有LCA为 x x x的链 ( u , v , w ) (u,v,w) (u,v,w),统计 u u u v v v路径上选择的点数。若不足 w w w,那么需要在 x x x处补充 w − c n t w-cnt wcnt个点。只需要数据结构支持单点修改、查询链和。不妨令 f x f_x fx表示 x x x到根路径点权和,那么单点修改对 f f f的影响是子树加,简单树状数组维护差分即可。

复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

1003. Forgiving Matchiing

【题意】

给定字符串 S , T S,T S,T,字符集有 0 ∼ 9 0\sim9 09和通配符’*’。对于每个 k ∈ [ 0 , ∣ T ∣ ] k\in[0, |T|] k[0,T],求出在 S S S中至多失配 k k k次,有多少个位置能匹配 T T T

∣ S ∣ , ∣ T ∣ ≤ 1 0 5 |S|,|T|\leq 10^5 S,T105

【思路】

一个典中典的问题。

没有通配符的话,我们只需要先反转 T T T,枚举匹配字符 c c c,令 a i = [ S i = = c ] , b i = [ T i = = c ] a_i=[S_i==c],b_i=[T_i==c] ai=[Si==c],bi=[Ti==c],FFT以后对应位置的值就是匹配的次数。

现在有通配符,可以考虑令 a i = [ S i = = c ] , b i = [ T i ! = c   & &   T i ! = ′ ∗ ′ ] a_i=[S_i==c],b_i=[T_i!=c\ \&\&\ T_i!='*'] ai=[Si==c],bi=[Ti!=c && Ti!=],求失配次数,最后统计答案即可。

复杂度 O ( 10 n log ⁡ n ) O(10n\log n) O(10nlogn)

比赛的时候统计答案写了个 O ( n 2 ) O(n^2) O(n2)的假了半天,还找了个跑的快的FFT,三发才发现是统计写假了。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=525000,mod=998244353,G=3;

namespace io {
    const int SIZE = (1 << 21) + 1;
    char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
    // getchar
    #define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
    // print the remaining part
    inline void flush () {
        fwrite (obuf, 1, oS - obuf, stdout);
        oS = obuf;
    }
    // putchar
    inline void putc (char x) {
        *oS ++ = x;
        if (oS == oT) flush ();
    }
    // input a signed integer
    template <class I>
    inline void gi (I &x) {
        for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
        for (x = 0; c <= '9' && c >= '0'; c = gc()) x = (x << 1) + (x << 3) + (c & 15); x *= f;
    }
    // print a signed integer
    template <class I>
    inline void print (I x) {
        if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;
        while (x) qu[++ qr] = x % 10 + '0',  x /= 10;
        while (qr) putc (qu[qr --]);
    }
    //no need to call flush at the end manually!
    struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io :: gi;
using io :: putc;
using io :: print;

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}
/*void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10^48);
}
void writeln(int x)
{
    write(x);
    puts("");
}*/

const int L=1<<19|1;
const double pi=acos(-1.0);
struct cp{
    double r,i;
    inline cp operator *(const cp &rhs)const{
        return {r*rhs.r-i*rhs.i,r*rhs.i+i*rhs.r}; 
    }
    inline void operator +=(const cp &rhs){
        r+=rhs.r;i+=rhs.i;
    }
    inline cp operator +(const cp &rhs)const{
        return {r+rhs.r,i+rhs.i};
    }
    inline cp operator -(const cp &rhs)const{
        return {r-rhs.r,i-rhs.i};
    }
}; 
namespace fft{ // by NaCly_Fish 
    cp rt[L];
    int rev[L],lim,l;
    
    inline void init(ri n){
        ri i;
        lim=1;l=0;
        while(lim<=n) lim<<=1,++l;
        cp w={cos(2*pi/lim),sin(2*pi/lim)};
        rt[lim>>1]={1,0};
        for(i=(lim>>1)+1;i!=lim;++i) rt[i]=rt[i-1]*w;
        for(i=(lim>>1)-1;i;--i) rt[i]=rt[i<<1];
        for(i=1;i^lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
    }
    inline void transform(cp *f,ri lim,ri type,ri siz){
        if(type==-1) reverse(f+1,f+lim);
        static cp a[L];
        ri shift=siz-__builtin_ctz(lim),i,mid,j,k;
        cp x;
        for(i=0;i^lim;++i) a[rev[i]>>shift]=f[i];
        for(mid=1;mid^lim;mid<<=1)
            for(j=0;j^lim;j+=mid<<1)
                for(k=0;k^mid;++k)
                    x=a[j|k|mid]*rt[mid+k],
                    a[j|k|mid]=a[j|k]-x,
                    a[j|k]+=x;
        for(i=0;i^lim;++i) f[i]=a[i];
        if(type==1) return;
        for(i=0;i^lim;++i) f[i].i/=lim;
    }
}
cp a[L];

int n,m;
int d[N],f[N],ans[N];
char s[N],t[N];

int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    for(int T=read();T--;)
    {
        n=read();m=read();fft::init(n+m);
        scanf("%s%s",s,t);reverse(s,s+n);
        for(int i=0;i<n;++i) d[i]=ans[i]=0;
        for(int c=0;c<10;++c)
        {
            for(int i=0;i<=fft::lim;++i) f[i]=a[i].r=a[i].i=0;
            for(int i=0;i<n;++i) a[i].r=(s[i]-'0'==c);
            for(int i=0;i<m;++i) a[i].i=(t[i]-'0'!=c && t[i]!='*');
            fft::transform(a,fft::lim,1,fft::l);
            for(int i=0;i^fft::lim;++i) a[i]=a[i]*a[i];
            fft::transform(a,fft::lim,-1,fft::l);
            for(int i=m-1;i<n;++i) d[i]+=(int)(a[i].i/2+0.5);
        }
        //printf("d::\n");
        //for(int i=0;i<n;++i) printf("%d ",d[i]);
        //puts("");
        for(int i=m-1;i<n;++i) ++ans[d[i]];
        for(int k=0;k<=m;++k) ans[k]+=ans[k-1],printf("%d\n",ans[k]);
        /*for(int i=m-1;i<n;++i)
        {
            for(int k=0;k<=m;++k) if(d[i]<=m-k) ++ans[k];
        }
        for(int i=m;i>=0;--i) print(ans[i]),putc('\n');*/
        //puts("");
    }
    //cerr<<clock();
    return 0;
}
1004. Game on Plane

【题意】

给出 x O y xOy xOy平面上 n n n条直线,要求对于每个 k ∈ [ 1 , n ] k\in[1,n] k[1,n],求出所有选择 k k k条直线的情况下,你再任意画一条直线,最少产生交点数量的最大值。

【思路】

显然要让最小值最大,就是最小化斜率出现次数的最大值,那么不断从每个斜率选择一个即可。

用个堆,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int maxn=3e5+5,inf=1e9+1;
int n,m;
double dat[maxn],k[maxn];
int K[maxn];
int gcd(int x,int y){return y?gcd(y,x%y):x;}
struct node{
    int frq,k;
    bool operator<(const node&A)const{
        return frq>A.frq;
    }
};
int cnt[maxn],tot[maxn];
signed main(){
    // freopen("my.in","r",stdin);
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        rep(i,1,n) cnt[i]=tot[i]=0;
        priority_queue<node>q;
        rep(i,1,n){
            int x,y,a,b;
            scanf("%d%d%d%d",&x,&y,&a,&b);
            if(a==x){
                dat[i]=k[i]=inf;
            }
            else{
                dat[i]=k[i]=1.0*(b-y)/(a-x);
            }
        }
        sort(dat+1,dat+1+n);
        int cc=unique(dat+1,dat+1+n)-dat-1;
        rep(i,1,n){
            K[i]=lower_bound(dat+1,dat+1+cc,k[i])-dat;
            tot[K[i]]++;
        }

        rep(i,1,cc){
            q.push({0,i});
        }
        int mx=0;
        rep(i,1,n){
            node x=q.top();q.pop();
            mx=max(mx,++cnt[x.k]);
            printf("%d\n",i-mx);
            // cout<<i-mx<<hvie;
            if(tot[x.k]>cnt[x.k]){
                q.push({cnt[x.k],x.k});
            }
        }

    }
    return 0;
}
1005. Kart Race

我已经爬了。

1006. New Equipments

【题意】

n n n个工人每个人有一个 a i a_i ai n n n台机器每个人有一个 b j b_j bj,工人和机器有 m m m个不能匹配关系,选择匹配关系可以获得 a i + b j a_i+b_j ai+bj,每个工人和每台机器最多被匹配一次。

求对于匹配次数为 1 ∼ n 1\sim n 1n时的最大和,或无法匹配这么多。

n ≤ 4000 , m ≤ 10000 n\leq 4000,m\leq 10000 n4000,m10000

【思路】

考虑一个经典的费用流建图:

  • 左边 n n n个点表示 n n n个工人,由 S S S向第 i i i个工人连边,容量1,费用 a i a_i ai
  • 右边 n n n个点表示 n n n台机器,由第 i i i台机器向 T T T连边,容量1,费用 b i b_i bi
  • 工人 i i i能匹配机器 j j j,从左边 i i i向右边 j j j连边,容量1,费用0

那么就是增广 1 ∼ n 1\sim n 1n次时的费用和。但由于边数太多,所以不能直接建图。

每次增广路长度是 O ( n ) O(n) O(n)的,算法瓶颈在找增广路。

在每一次增广中,我们需要找到一条$S 到 到 T 的 费 用 最 大 的 路 径 , 注 意 到 非 0 费 用 只 会 出 现 在 每 个 点 连 到 源 汇 的 边 上 , 因 此 等 价 于 寻 找 一 个 工 人 的费用最大的路径,注意到非0费用只会出现在每个点连到源汇的边上,因此等价于寻找一个工人 0x 和 一 个 设 备 和一个设备 y , 满 足 它 们 在 之 前 的 增 广 中 没 有 被 使 用 过 , ,满足它们在之前的增广中没有被使用过, 广使x 通 过 一 开 始 的 边 以 及 增 广 后 添 加 的 通过一开始的边以及增广后添加的 广O(n) 条 用 于 反 悔 的 匹 配 边 可 以 到 达 条用于反悔的匹配边可以到达 y , 且 ,且 a_x + b_y 最 大 。 枚 举 右 侧 每 个 点 最大。枚举右侧每个点 y , 只 要 找 到 左 侧 能 到 达 它 的 点 权 最 大 的 点 , 那 么 就 可 以 找 到 最 优 的 点 对 以 及 对 应 的 增 广 路 。 按 照 ,只要找到左侧能到达它的点权最大的点,那么就可以找到最优的点对以及对应的增广路。按照 广a 从 大 到 小 依 次 从 左 侧 每 个 点 开 始 B F S , 记 录 每 个 点 第 一 次 被 左 侧 哪 个 点 遍 历 到 , 那 么 它 就 表 示 左 侧 能 到 达 它 的 点 权 最 大 的 点 。 在 这 里 由 于 图 是 用 补 图 的 方 式 给 出 的 , 需 要 使 用 补 图 B F S 的 方 式 , 即 维 护 目 前 还 未 遍 历 过 的 点 集 从大到小依次从左侧每个点开始BFS,记录每个点第一次被左侧哪个点遍历到,那么它就表示左侧能到达它的点权最大的点。在这里由于图是用补图的方式给出的,需要使用补图BFS 的方式,即维护目前还未遍历过的点集 BFS使BFSR , 遍 历 到 点 ,遍历到点 x 时 , 将 时,将 R 修 改 为 修改为 x$ 在补图中的出边的点集,由于补图只有$m 条 边 , 因 此 按 照 这 种 方 式 B F S 的 时 间 复 杂 度 为 条边,因此按照这种方式BFS 的时 间复杂度为 BFSO(n + m) 。 一 共 要 增 广 。一共要增广 广n 轮 , 每 轮 寻 找 增 广 路 的 时 间 复 杂 度 为 轮,每轮寻找增广路的时间复杂度为 广O(n + m)$,故总时间复杂度为
O ( n 2 + n m ) O(n^2 + nm) O(n2+nm)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=4005;

int n,m,S,lim;
queue<int>q;
vector<int>G[N];
int a[N],b[N],c[N];
int fl[N],fr[N];
int que[N],vis[N],from[N],pre[N],flag[N];

void insert(int x)
{
    if(vis[x]) return;
    vis[x]=1;
    int cnt=0;
    for(auto v:G[x]) flag[v]=1;
    for(int i=1;i<=lim;++i)
    {
        int t=que[i];
        if(from[t]) continue;
        if(flag[t]) {que[++cnt]=t;continue;}
        from[t]=S;pre[t]=x;
        q.push(t);
    }
    lim=cnt;
    for(auto v:G[x]) flag[v]=0;
}
void bfs(int x)
{
    while(!q.empty()) q.pop();
    insert(x);
    while(!q.empty())
    {
        x=q.front();q.pop();
        if(fr[x]) insert(fr[x]);
    }
}
int getans()
{
    int ret=-1,lp,rp,now,tmp;
    for(int i=1;i<=n;++i) vis[i]=from[i]=0,que[i]=i;

    lim=n;
    for(int i=1;i<=n;++i)
    {
        S=c[i];
        if(!fl[c[i]]) bfs(c[i]);
    }
    for(int i=1;i<=n;++i) if(!fr[i] && from[i])
    {
        now=b[i]+a[from[i]];
        if(now>ret) ret=now,rp=i;
    }
    if(ret<0) return -1;
    lp=from[rp];
    while(pre[rp]!=lp) tmp=pre[rp],fr[rp]=tmp,swap(rp,fl[tmp]);
    fl[lp]=rp;fr[rp]=lp;
    return ret;
}
bool cmp(int x,int y){return a[x]>a[y];}

int main()
{
    //freopen("ttt.in","r",stdin);
    //freopen("a.out","w",stdout);
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) G[i].clear(),fl[i]=fr[i]=0;
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        for(int i=1;i<=n;++i) scanf("%d",&b[i]);
        for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].pb(v); 
        for(int i=1;i<=n;++i) c[i]=i;
        sort(c+1,c+n+1,cmp);

        ll ans=0;
        for(int i=1;i<=n;++i)
        {
           // cerr<<i<<endl;
            int now=getans();
            if(now<0)
            {
                for(;i<=n;++i) puts("-1");
                break;
            }
            ans+=now;
            printf("%lld\n",ans);
        }
        //cerr<<T<<endl;
    }
    return 0;
}
 
1007.Photoshop Layers

队友做的签,懒得看了

1008. Restore Atlantis 2

看了一眼题解就不想做了。

1009. Rise in Price

【题意】

一个 n × n n\times n n×n的网格,每个网格中有两个正权值 ( a , b ) (a,b) (a,b),你从 ( 1 , 1 ) (1,1) (1,1)只能往下或往右走到 ( n , n ) (n,n) (n,n),求 ( ∑ a ) × ( ∑ b ) (\sum a)\times (\sum b) (a)×(b)的最大值。

n ≤ 100 n\leq 100 n100,数据为纯随机生成。

【思路】

随机=乱搞

分别记录下 ∑ a , ∑ b , ∑ a b \sum a,\sum b,\sum ab a,b,ab最大的2000组,归并进行转移。

复杂度 O ( 2000 n 2 ) O(2000n^2) O(2000n2)

【参考代码】

/*
 * @date:2021-07-27 13:43:37
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

const int MAXN = 100 + 5;
const int LIMIT = 2000;

int T, N;
ll A[MAXN][MAXN], B[MAXN][MAXN];
struct Node {
    ll sum[2];
};
vector<Node> F[3][MAXN][MAXN];

bool cmp(int k, int x, int y, Node &a, Node &b) {
    if (k < 2) return a.sum[k] > b.sum[k];
    return (a.sum[0] + A[x][y]) * (a.sum[1] + B[x][y]) >
           (b.sum[0] + A[x][y]) * (b.sum[1] + B[x][y]);
}

void merge(int x, int y) {
    for (int k = 0; k < 2; ++k) {
        auto &left = F[k][x][y - 1];
        auto &up = F[k][x - 1][y];
        auto &now = F[k][x][y];
        now.clear();
        int p1 = 0, p2 = 0;
        int sz = 0, sza = SZ(up), szb = SZ(left);
        while (p1 < sza && p2 < szb) {
            if (sz == LIMIT) break;
            if (cmp(k, x, y, up[p1], left[p2])) {
                if (now.empty() || up[p1].sum[0] > now.back().sum[0] || up[p1].sum[1] > now.back().sum[1]) {
                    now.pb(up[p1]);
                    ++sz;
                }
                p1++;
            } else {
                if (now.empty() || left[p2].sum[0] > now.back().sum[0] || left[p2].sum[1] > now.back().sum[1]) {
                    now.pb(left[p2]);
                    ++sz;
                }
                p2++;
            }
        }
        while (sz < LIMIT && p1 < sza) {
            if (up[p1].sum[0] > now.back().sum[0] || up[p1].sum[1] > now.back().sum[1]) {
                now.pb(up[p1]);
                ++sz;
            }
            p1++;
        }
        while (sz < LIMIT && p2 < szb) {
            if (left[p2].sum[0] > F[k][x][y].back().sum[0] || left[p2].sum[1] > now.back().sum[1]) {
                now.pb(left[p2]);
                ++sz;
            }
            p2++;
        }
    }
}

void solve() {
    scanf("%d", &N);
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= N; ++j) {
            scanf("%lld", &A[i][j]);
        }
    }
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= N; ++j) {
            scanf("%lld", &B[i][j]);
        }
    }
    for (int i = 0; i < 2; ++i) {
        F[i][1][1].clear();
        F[i][1][1].pb({A[1][1], B[1][1]});
    }
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= N; ++j) {
            if (i == 1 && j == 1) continue;
            if (i == 1)
                for (int k = 0; k < 2; ++k) F[k][i][j] = F[k][i][j - 1];
            if (j == 1)
                for (int k = 0; k < 2; ++k) F[k][i][j] = F[k][i - 1][j];
            if (i > 1 && j > 1) merge(i, j);
            for (int k = 0; k < 2; ++k) {
                Trav(a, F[k][i][j]) {
                    a.sum[0] += A[i][j];
                    a.sum[1] += B[i][j];
                }
            }
        }
    }
    ll mx = 0;
    for (int i = 0; i < 2; ++i) {
        Trav(x, F[i][N][N]) {
            chkMax(mx, x.sum[0] * x.sum[1]);
        }
    }
    printf("%lld\n", mx);
}

int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    scanf("%d", &T);
    while (T--) solve();
    //cerr << clock() << endl;
    return 0;
}
1010. Road Discount

【题意】

一幅 n n n个点 m m m条边的无向图,每条边有一个权值 a i a_i ai和一个折扣权值 b i b_i bi,对于每个 k ∈ [ 0 , n − 1 ] k\in[0,n-1] k[0,n1],分别求出恰好 k k k条边使用折扣时的MST

n ≤ 1000 , m ≤ 2 × 1 0 5 , a i ≤ b i ≤ 1000 n\leq 1000,m\leq 2\times 10^5,a_i\leq b_i\leq 1000 n1000,m2×105,aibi1000,有10组数据

【思路】

这个边权很小,一看就和枚举有关。

考虑这种恰好选 k k k条边的玩意,一个典中典的问题是有一堆白边一堆黑边,求出选择恰好 k k k条黑边的MST,这个东西我们只需要二分一个权值 c c c,然后给每条白边加上 c c c,分别求出MST中黑边数量最小值和最大值,看看 k k k是否在这之间即可。

f ( k ) f(k) f(k)为恰好 k k k条黑边的MST边权和,可知这是一个凸函数, f ( k ) = s u m ( c ) − k × c f(k)=sum(c)-k\times c f(k)=sum(c)k×c

那么对于这个问题,我们把原边权看作白边,折扣边看作黑边,然后枚举这个 c c c即可。

但是这样先对黑边白边排序后,归并得到后面的结果,复杂度是 O ( T m c ) O(Tmc) O(Tmc)的,并不能过(实际上比赛可以卡过)

一个结论是,所有用到的边只会是全部使用黑边和全部使用白边得到的MST中的边。

那么复杂度就变成 O ( T n c ) O(Tnc) O(Tnc)的了。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2005,M=4e5+10,mod=998244353,G=3;

int n,m,cnt;
int fa[N],ans[N];

struct Tway
{
    int u,v,w,k;
}e[M];
bool cmp(const Tway&a,const Tway&b){return a.w<b.w;}
bool cmp0(const Tway&a,const Tway&b){return a.w==b.w?a.k<b.k:a.w<b.w;}
bool cmp1(const Tway&a,const Tway&b){return a.w==b.w?a.k>b.k:a.w<b.w;}
int findf(int x){return fa[x]==x?x:fa[x]=findf(fa[x]);}

void init()
{
    scanf("%d%d",&n,&m);cnt=0;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        e[i+m]=e[i];scanf("%d",&e[i+m].w);
        e[i].k=0;e[i+m].k=1;
    }
    sort(e+1,e+m+1,cmp);sort(e+m+1,e+2*m+1,cmp);

    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=m;++i)
    {
        int u=findf(e[i].u),v=findf(e[i].v);
        if(u==v) continue;
        e[++cnt]=e[i];fa[u]=v;
    }
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=m+1;i<=m*2;++i)
    {
        int u=findf(e[i].u),v=findf(e[i].v);
        if(u==v) continue;
        e[++cnt]=e[i];fa[u]=v;
    }
    m=cnt/2;
}


void solve(int delta)
{
    int cnt0=0,cnt1=0,sum=0,l,r,res;

    l=1;r=m+1;res=n-1;
    for(int i=1;i<=n;++i) fa[i]=i;
    for(;res;)
    {
        if(e[l].w<=e[r].w+delta)
        {
            int u=findf(e[l].u),v=findf(e[l].v);++l;
            if(u==v) continue;
            --res;sum+=e[l-1].w;fa[u]=v; 
        }
        else
        {
            int u=findf(e[r].u),v=findf(e[r].v);++r;
            if(u==v) continue;
            --res;sum+=e[r-1].w+delta;fa[u]=v;++cnt0;
        }
    }

    l=1;r=m+1;res=n-1;
    for(int i=1;i<=n;++i) fa[i]=i;
    for(;res;)
    {
        if(e[l].w<e[r].w+delta)
        {
            int u=findf(e[l].u),v=findf(e[l].v);++l;
            if(u==v) continue;
            --res;fa[u]=v; 
        }
        else
        {
            int u=findf(e[r].u),v=findf(e[r].v);++r;
            if(u==v) continue;
            --res;fa[u]=v;++cnt1;
        }
    }
    for(int i=cnt0;i<=cnt1;++i) ans[i]=sum-delta*i;

    /*for(int i=1;i<=m;++i) 
        if(e[i].k) e[i].w+=delta;

    sort(e+1,e+m+1,cmp0);
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=m;++i)
    {
        int u=findf(e[i].u),v=findf(e[i].v);
        if(u==v) continue;
        fa[u]=v;sum+=e[i].w;
        if(e[i].k) ++cnt0;
    }

    sort(e+1,e+m+1,cmp1);
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=m;++i)
    {
        int u=findf(e[i].u),v=findf(e[i].v);
        if(u==v) continue;
        fa[u]=v;
        if(e[i].k) ++cnt1;
    }

    for(int i=1;i<=m;++i)
        if(e[i].k) e[i].w-=delta;

    for(int i=cnt0;i<=cnt1;++i) ans[i]=sum-delta*i;
    //printf("%d %d %d\n",delta,cnt0,cnt1);*/
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        init();
        //puts("");
        //for(int i=1;i<=m;++i) printf("%d %d %d %d\n",e[i].u,e[i].v,e[i].w,e[i].k);
        for(int c=-1000;c<=1000;++c) solve(c);
        for(int i=0;i<n;++i) printf("%d\n",ans[i]);
    }
    return 0;
}
 
1011. Segment Tree with Pruning

队友写的签,好像是个纯暴力记忆化。

1012. Tree Planting

【题意】

一个长度为 n n n的序列 w i w_i wi,一个合法的 c i c_i ci满足:

  • c i ∈ { 0 , 1 } c_i\in\{0,1\} ci{0,1}

  • c i c_i ci c i + 1 c_{i+1} ci+1不同时为1

  • c i c_i ci c i + k c_{i+k} ci+k不同时为1

一个合法的 c i c_i ci的权值是位置为1的地方对应 w i w_i wi的乘积,求所有合法的 c i c_i ci的权值和对一个常见素数取模的值。

【思路】

比赛的时候想到了,但没完全想到。

首先考虑怎么解决这个限制,我们可以把一维变成二维,即变换为一个 ⌈ n k ⌉ \lceil\frac {n} k\rceil kn行, k k k列的网格,这样限制就变成了上下左右不能同时为1,且每一行的最后一个和下一行的第一个不能同时为1。

比赛的时候想的是如果 k k k比较小,直接状压就行,如果比较大可以交换行列状压,可行状态数很少。

但是实际上,行列交换以后,每行最后一个与下一行第一个的限制就变成了每列第一个和下一列最后一个的限制,这样就不太能做了。

题解给给出的做法是从左往右轮廓线DP,设 f i , j , S , T f_{i,j,S,T} fi,j,S,T表示考虑到了 ( i , j ) (i,j) (i,j)这个格子,第一行种树情况为 S S S,轮廓线上种树情况为 T T T的总贡献,复杂度 O ( n 2 2 n k ) O(n2^{\frac {2n} k}) O(n2k2n),但由于 S S S T T T是独立集,状态很少, k k k个点的独立集数量为斐波那契数列的第 k k k项,于是复杂度就是 O ( 1.618 n 2 n k ) O(1.618n\frac {2n} k) O(1.618nk2n) k k k比较小的时候就按前面的方法状压就行。

【参考代码】

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=305,P=1000000007;
int Case,n,m,i,w[N];
inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;}
inline void upl(int&a,ll b){a=(a+b)%P;}
namespace SMALL{
const int MAXL=121505;
int cnt,q[MAXL],g[MAXL][2];
map<int,int>id;
int pre[MAXL],now[MAXL];
inline void clr(){for(int i=1;i<=cnt;i++)now[i]=0;}
inline void nxt(){for(int i=1;i<=cnt;i++)pre[i]=now[i];}
void dfs(int x,int S){
  if(x==m){
    id[S]=++cnt;
    q[cnt]=S;
    return;
  }
  dfs(x+1,S<<1);
  if(!(S&1))dfs(x+1,S<<1|1);
}
void solve(){
  //m<=24
  cnt=0;
  id.clear();
  dfs(0,0);
  int i,j,S,T,x;
  for(i=1;i<=cnt;i++){
    S=q[i],T=(S<<1)&((1<<m)-1);
    g[i][0]=id[T];
    g[i][1]=0;
    if(!(S>>(m-1)&1))g[i][1]=id[T|1];
  }
  clr();
  now[1]=1;
  nxt();
  for(i=0;i<n;i++){
    clr();
    x=w[i];
    for(j=1;j<=cnt;j++){
      up(now[g[j][0]],pre[j]);
      upl(now[g[j][1]],1LL*pre[j]*x);
    }
    nxt();
  }
  int ans=P-1;
  for(i=1;i<=cnt;i++)up(ans,pre[i]);
  printf("%d\n",ans);
}
}
namespace BIG{
const int K=15,MAXL=505;
int r,c,val[N][K],cnt[K],q[K][MAXL],g[K][MAXL][2];
map<int,int>id[K];
int pre[MAXL],now[MAXL];
inline void clr(int cnt){for(int i=1;i<=cnt;i++)now[i]=0;}
inline void nxt(int cnt){for(int i=1;i<=cnt;i++)pre[i]=now[i];}
inline bool check(int S,int l,int r){
  for(int i=l;i<r;i++)if((S>>i&1)&&(S>>(i+1)&1))return 0;
  return 1;
}
void solve(){
  //c=ceil(n/m)<=12,r>1
  r=c=0;
  int i,j,k,o,S,T,x,y;
  for(i=0;i<n;i++)r=max(r,i%m),c=max(c,i/m);
  r++,c++;
  for(i=0;i<r;i++)for(j=0;j<c;j++)val[i][j]=0;
  for(i=0;i<n;i++)val[i%m][i/m]=w[i];
  for(i=0;i<c;i++){
    //[0..i-1] [i..c-1]
    cnt[i]=0;
    id[i].clear();
    for(S=0;S<1<<c;S++){
      if(!check(S,0,i-1))continue;
      if(!check(S,i,c-1))continue;
      id[i][S]=++cnt[i];
      q[i][cnt[i]]=S;
    }
  }
  for(i=0;i<c;i++)for(j=1;j<=cnt[i];j++){
    S=q[i][j];
    x=S>>i&1;
    T=S^(x<<i);
    g[i][j][0]=id[(i+1)%c][T];
    g[i][j][1]=0;
    if(x)continue;
    g[i][j][1]=id[(i+1)%c][S|(1<<i)];
  }
  int ans=P-1;
  for(o=1;o<=cnt[0];o++){
    clr(cnt[0]);
    S=q[0][o];
    int tmp=1;
    for(i=0;i<c;i++)if(S>>i&1)tmp=1LL*tmp*val[0][i]%P;
    now[o]=tmp;
    nxt(cnt[0]);
    for(i=1;i<r;i++)for(j=0;j<c;j++){
      int A=cnt[j],B=cnt[(j+1)%c],C=val[i][j];
      clr(B);
      for(k=1;k<=A;k++){
        up(now[g[j][k][0]],pre[k]);
        upl(now[g[j][k][1]],1LL*pre[k]*C);
      }
      nxt(B);
    }
    for(i=1;i<=cnt[0];i++)if(!((S>>1)&q[0][i]))up(ans,pre[i]);
  }
  printf("%d\n",ans);
}
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&m);
    for(i=0;i<n;i++)scanf("%d",&w[i]);
    if(m*m<=n*2)SMALL::solve();else BIG::solve();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值