11.20

\(100+100+30+45=275\)
开场 \(10\) 分钟,开始码 A,码了一个小时无果删码跑路。
十几分钟把 B 切了开始单挑 C,失败。
写完 D 暴力后剩 30min,极限写完 A。

A: 小 H 的积木

把最上方和最下方的都加入线段树,如果有一个数加入了 \(2\) 次,就把它放入待选答案里面,每次选择最小的同时放入与他相邻的数。

点击查看代码
#include <bits/stdc++.h>
#define fr first
#define se second
#define Un unsigned
#define LL long long
#define pb push_back
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define __ __int128
#define LD long double
#define VE vector<LL>
#define DB double
#define Ve vector<int>
 
using namespace std;
 
inline LL read()
{
    LL x = 0,f = 1;char ch = getchar();
    while(!isdigit(ch)) (ch == '-') && (f = -1),ch = getchar();
    while(isdigit(ch)) x = x*10+ch-48,ch = getchar();
    return x*f;
}
 
const int N = 2e5+5,INF = 2e9;
vector < pii > pos[N];
Ve a[N],L,R;
unordered_map < int,bool > hd[N],tl[N],in[N];
bool vis[N];
 
struct tree{int l,r,cnt;pii mn;}tr[N<<2];
 
void build(int l,int r,int i)
{
    tr[i] = {l,r,0,{INF,0}};
    if (l == r) return ;
    int mid = (l+r)>>1;
    build(l,mid,i<<1),build(mid+1,r,i<<1|1);
}
 
void M(int p,int k,int i)
{
    if (tr[i].l == tr[i].r)
    {
        tr[i].cnt += k;
        if (tr[i].cnt >= 2)
        {
            auto [a,b] = pos[tr[i].l][0];
            auto [c,d] = pos[tr[i].l][1];
            if ((hd[a][b] && tl[c][d]) || (tl[a][b] && hd[c][d]))
            {
                if (hd[a][b])
                {
                    if (tl[c][d]) tr[i].mn = {a,tr[i].l};
                    else tr[i].mn = {c,tr[i].l};
                }
                else tr[i].mn = {c,tr[i].l};
            }
            else tr[i].mn = {INF,0};
        }
        else tr[i].mn = {INF,0};
        return ;
    }
    if (p <= tr[i<<1].r) M(p,k,i<<1);
    else M(p,k,i<<1|1);
    tr[i].mn = min(tr[i<<1].mn,tr[i<<1|1].mn);
}
 
int main()
{
    int n = read(),m = read();
    for (int i = 1,k;i <= m;i++)
    {
        k = read();
        for (int j = 0,x;j < k;j++) x = read(),a[i].pb(x),pos[x].pb({i,j});
    }
    for (int i = 1;i <= n;i++)
    {
        if (pos[i].size() != 2) return puts("-1"),0;
        sort(pos[i].begin(),pos[i].end());
    }
    build(1,n,1);
    for (int i = 1;i <= m;i++)
    {
        hd[i][0] = 1,tl[i][a[i].size()-1] = 1,M(a[i][0],1,1);
        if (a[i].size() > 1)
            M(a[i].back(),1,1);
    }
    for (int i = 1;i <= n;i++)
    {
        auto [x,y] = tr[1].mn;
        if (x == INF) return puts("-1"),0;
        auto [u,v] = pos[y][0];
        auto [e,f] = pos[y][1];
        M(y,-1e9,1),vis[y] = 1;
        L.pb(x);
        if (x == u)
        {
            R.pb(e);
            if (v+1 < a[u].size() && !vis[a[u][v+1]]) hd[u][v+1] = 1,M(a[u][v+1],1,1);
            if (f-1 >= 0 && !vis[a[e][f-1]])
            {
                tl[e][f-1] = 1,M(a[e][f-1],0,1);
                if (!hd[e][f-1]) M(a[e][f-1],1,1);
            }
        }
        else
        {
            R.pb(u);
            if (f+1 < a[e].size() && !vis[a[e][f+1]])
                hd[e][f+1] = 1,M(a[e][f+1],1,1);
            if (v-1 >= 0 && !vis[a[u][v-1]])
            {
                tl[u][v-1] = 1,M(a[u][v-1],0,1);
                if (!hd[u][v-1]) M(a[u][v-1],1,1);
            }
        }
    }
    for (int i : L) printf("%d ",i);
    reverse(R.begin(),R.end());
    for (int i : R) printf("%d ",i);
    return 0;
}

B: 小 H 的树

有两种方式,一种是断树边,另一种是把环断开,找出来环断环为链双指针搞一下即可。

点击查看代码
#include <bits/stdc++.h>
#define fr first
#define se second
#define Un unsigned
#define LL long long
#define pb push_back
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define __ __int128
#define LD long double
#define VE vector<LL>
#define DB double
#define Ve vector<int>
 
using namespace std;
 
inline LL read()
{
    LL x = 0,f = 1;char ch = getchar();
    while(!isdigit(ch)) (ch == '-') && (f = -1),ch = getchar();
    while(isdigit(ch)) x = x*10+ch-48,ch = getchar();
    return x*f;
}
 
const int N = 2e5+5;
struct edge{int to,pre;}e[N<<1];
int a[N],las[N],cnt,pre[N],r[N<<1],ct;
bool in[N],flg,vis[N];
LL sum,s[N],ans = 1e18;
 
void add(int u,int v){e[++cnt] = {v,las[u]},las[u] = cnt;}
 
void D(int x)
{
    if (flg) return ;
    vis[x] = 1;
    for (int i = las[x],y;i && !flg;i = e[i].pre)
    {
        if ((y = e[i].to) != pre[x])
        {
            if (vis[y])
            {
                r[++ct] = y,in[y] = 1,flg = 1;
                while(x != y) r[++ct] = x,in[x] = 1,x = pre[x];
            }
            else pre[y] = x,D(y);
        }
    }
}
 
void D1(int x,int fa)
{
    s[x] = a[x];
    for (int i = las[x],y;i;i = e[i].pre)
        if ((y = e[i].to) != fa && !in[y]) D1(y,x),s[x] += s[y];
    if(!in[x]) ans = min(ans,llabs(sum-2*s[x]));
}
 
int main()
{
    int n = read();
    for (int i = 1;i <= n;i++) sum += (a[i] = read());
    for (int i = 1,u,v;i <= n;i++)
        u = read(),v = read(),add(u,v),add(v,u);
    D(1);
    for (int i = 1;i <= ct;i++) D1(r[i],0),r[i+ct] = r[i];
    LL res = 0;int j = 1;
    for (int i = 1;i <= ct;i++)
    {
        while(j < i+ct && res < sum/2) res += s[r[j++]];
        ans = min(ans,llabs(sum-2*res));
        res -= s[r[i]];
    }
    cout << ans;
    return 0;
}****

C: 小 H 的串

发现一种答案可能对应多种操作,赛时试图使用脑袋中仅剩的 \(\text{Trick}\) :把答案和操作间形成单射,对应同一答案的操作序列只在字典序最小时统计。但是根本进行不下去。

于是放弃对操作进行计数,我们对合法答案计数即可。
由于 \(t\) 很短,所以考虑状压。
\(f_{i, sta}\) 表示结果串长 \(i\),匹配状态为 \(sta\) 的结果串个数。

匹配状态 \(sta\):若 \(sta\)\(j\)\(1\),那么 存在一种用 \(s1_{1\ldots i - j}\)\(s2_{1\ldots j}\) 拼出结果串的操作序列。

转移时枚举 \(i + 1\) 位填什么字符求下一个状态转移。
解析:

D: 小 H 的蛋糕F. Array Covering

wqs 二分大法好。

posted @ 2024-11-20 22:52  ZepX_D  阅读(25)  评论(0)    收藏  举报