2022NOIP A层联测33 GCD 简单题 建筑 树上前缀和

T1:[图论/枚举]给出有边权无向图,边权保证互不相同,Q次询问从S到T的路径中,边权的gcd最大是多少。(n<=1e4,Q<=2e5,w<=1e6)

考场

根据之前的一道图论题经验,在最短路上加个“\(w=(reach_{time}+ci)/di\)”或者是取个gcd都可以玄学无脑跑最短路,于是上来打了个dij最长路(考虑到边权不同,所以一定是递减,那么经过这条边减少的价值就看成是负边权,所以没有正环,妥妥的Dij啊!)

正解

首先考场思路假了,因为gcd的取值和数本身的大小没有关系,换句话说,保留更新x的最大值不代表决策最优性,比如x=1e9+7>x=8,但是如果接下来的边是4,那么显然8更优。

考虑正解是怎么做的,贪心假了,只能枚举了。枚举gcd从大到小,这样询问第一次被update就是答案,就可以删除了。以x为gcd构成若干联通图,询问的s和t如果在同一联通图内x就是答案。

【1】快速确定联通块的边:边权开桶直接加【2】快速确定连通块和询问交集:bitset维护点的询问集合以及点的可达集合

卡常:【1】只操作本次边涉及到的集合【2】清空紧贴使用,不额外加复杂度

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const int N = 3e4 + 10, Q = 3e5 + 10;
int maxn, n, m, q;
struct edge {
    int u, v;
} e[(int)1e5 + 100];
struct que {
    int u, v;
} qr[Q];
bitset<30001> s[N], g[N];  //对每个点维护询问集合?
int mx, fa[N];
vector<int> pt;
bool vis[N];
unordered_map<int, int> ans[Q];
inline void init() {
    for (rint i = 1; i <= n; ++i) fa[i] = i, g[i][i] = 1;
}
inline int father(int x) { return (x == fa[x]) ? fa[x] : (fa[x] = father(fa[x])); }
inline void solve(int u, int x)  //处理这个点
{
    g[u] |= g[father(u)];
    //  for(rint i=1;i<=n;++i)if(g[u][i])chu("has the coed:%d %d\n",u,i);
    g[u] &= s[u];
    //   chu("deal with:%d\n",u);
    //   for(rint i=1;i<=n;++i)if(s[u][i])chu("has queru:%d %d\n",u,i);
    for (rint i = g[u]._Find_first(); i != g[u].size(); i = g[u]._Find_next(i)) {
        // chu("it has :%d\n",i);
        ans[u][i] = x;
        s[u][i] = 0;
        g[u][i] = 0;
    }
    g[u][u] = 1;
}
inline void unit(int u, int v) {
    int fu = father(u), fv = father(v);
    if (fu == fv)
        return;
    if (!vis[u])
        pt.push_back(u), vis[u] = 1;
    if (!vis[v])
        pt.push_back(v), vis[v] = 1;
    g[fu] |= g[fv];
    fa[fv] = fu;
}
int main() {
    //  freopen("1.in","r",stdin);
    // freopen("2.out","w",stdout);
    freopen("gcd.in", "r", stdin);
    freopen("gcd.out", "w", stdout);
    n = re(), m = re(), q = re();
    for (rint i = 1; i <= m; ++i) {
        int u = re(), v = re(), w = re();
        e[w] = (edge){ u, v };
        mx = max(mx, w);
    }
    for (rint i = 1; i <= q; ++i) {
        qr[i].u = re(), qr[i].v = re();
        s[qr[i].u].set(qr[i].v);
    }
    init();
    // printf("k:%d\n",mx);
    for (rint i = mx; i >= 1; --i) {
        // chu("for try:%d\n",i);
        for (int j = i; j <= mx; j += i) {
            if (e[j].u)
                unit(e[j].u, e[j].v);  // chu("add:%d %d\n",e[j].u,e[j].v);
        }
        //  for(rint j=1;j<=n;++j)if(g[1][j])chu("has 1:%d %d\n",1,j);
        for (rint j : pt)
            if (fa[j] != j)
                solve(j, i);
        for (rint j : pt)
            if (fa[j] == j)
                solve(j, i);
        for (rint to : pt) vis[to] = 0, fa[to] = to;
        pt.clear();
    }
    for (rint i = 1; i <= q; ++i) {
        if (ans[qr[i].u].count(qr[i].v) == 0)
            chu("-1\n");
        else
            chu("%d\n", ans[qr[i].u][qr[i].v]);
    }
    return 0;
}
/*
5 5 3
1 2 48
2 4 24
3 2 32
1 3 18
3 4 96
1 3
1 4
3 5


【1】枚举gcd,枚举边,连图
【2】现在有若干连通块,枚举每个连通块处理询问:
枚举到块A的点k:
&一下,发现剩下了1,于是依次遍历每个位置上的1,处理询问,删除这个位置上的1

*/

T2[数据结构:线段树]给出序列A[x,y],有Q次询问:【1】opt=1,l,r:x+=y,y=phi[y]【2】opt=2,l,r:query_sum(x)and query_sum(y)。(n<=2e5)

考场

发现20次后phi=1,于是线段树维护了到目前的已经加了多少次(到20次就是1),维护2个值的区间和值,然后瞎搞了一堆线段树发现300行爆了,于是放弃

正解

只维护是否是y=1就行(否则到1了没到20次就浪费),而且分离询问和修改,修改特殊tag=1就直接+,询问按照正常写就行。

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
#define int ll
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const int N = 1e6 + 10, M = 2e5 + 100;
const int mod = 998244353;
int zhi[N], prime[N], p[N];
int n, m;
pair<int, int> a[(int)2e5 + 100];
inline void upd(int& x, int y) {
    x = x + y;
    x = (x >= mod) ? (x - mod) : x;
}
inline void shai() {
    p[1] = 1;
    int cnt = 0;
    for (rint i = 2; i <= 1000000; ++i) {
        if (!prime[i])
            zhi[++cnt] = i, p[i] = i - 1;
        for (rint j = 1; 1ll * zhi[j] * i <= 1000000; ++j) {
            int k = zhi[j] * i;
            prime[k] = 1;
            p[k] = p[i] * ((i % zhi[j]) ? (zhi[j] - 1) : zhi[j]);
            if (i % zhi[j] == 0)
                break;
        }
    }
}
struct segment {
    int sum1[M << 2], sum2[M << 2], tag[M << 2];
    bool cov[M << 2];
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define mid ((l + r) >> 1)
    inline void pushup(int rt, int l, int r) {
        sum1[rt] = sum1[lson] + sum1[rson];
        sum2[rt] = sum2[lson] + sum2[rson];
        sum1[rt] = (sum1[rt] >= mod) ? (sum1[rt] - mod) : sum1[rt];
        sum2[rt] = (sum2[rt] >= mod) ? (sum2[rt] - mod) : sum2[rt];
        cov[rt] = cov[lson] & cov[rson];
    }
    inline void pushdown(int rt, int l, int r) {
        if (!tag[rt])
            return;
        upd(sum1[lson], tag[rt] * (mid - l + 1));
        upd(sum1[rson], tag[rt] * (r - mid));
        upd(tag[lson], tag[rt]);
        upd(tag[rson], tag[rt]);
        tag[rt] = 0;
    }
    inline void build(int rt, int l, int r) {
        if (l == r) {
            sum1[rt] = a[l].first;
            sum2[rt] = a[l].second;
            return;
        }
        build(lson, l, mid);
        build(rson, mid + 1, r);
        pushup(rt, l, r);
        // chu("sum1[%d--%d]:%d\n",l,r,sum1[rt]);
    }
    inline void insert(int rt, int l, int r, int L, int R) {
        if (l == r) {
            upd(sum1[rt], sum2[rt]);
            sum2[rt] = p[sum2[rt]];
            cov[rt] = (sum2[rt] == 1);
            // chu("sum1[%d--%d]:%d\n",l,r,sum1[rt]);
            return;
        }
        pushdown(rt, l, r);
        if (cov[rt] && L <= l && r <= R)  //如果有标记,永远不需要下放,所以pushdown就很离谱
        {
            tag[rt]++;
            upd(sum1[rt], r - l + 1);
            return;
        }
        if (L <= mid)
            insert(lson, l, mid, L, R);
        if (R > mid)
            insert(rson, mid + 1, r, L, R);
        pushup(rt, l, r);
    }
    inline pair<int, int> query(int rt, int l, int r, int L, int R)  //询问一对查
    {
        if (L <= l && r <= R) {
            return make_pair(sum1[rt], sum2[rt]);
        }
        pushdown(rt, l, r);
        if (R <= mid)
            return query(lson, l, mid, L, R);
        if (L > mid)
            return query(rson, mid + 1, r, L, R);
        pair<int, int> r1, r2;
        r1 = query(lson, l, mid, L, R);
        r2 = query(rson, mid + 1, r, L, R);
        upd(r1.first, r2.first);
        upd(r1.second, r2.second);
        return r1;
    }
} seg;
signed main() {
    //  freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    freopen("simple.in", "r", stdin);
    freopen("simple.out", "w", stdout);
    shai();
    n = re(), m = re();
    int mx = 0;
    for (rint i = 1; i <= n; ++i) {
        a[i].first = re(), a[i].second = re();
    }
    seg.build(1, 1, n);
    for (rint i = 1; i <= m; ++i) {
        int opt = re(), l = re(), r = re();
        if (opt == 1) {
            //  chu("update:%lld %lld\n",l,r);
            seg.insert(1, 1, n, l, r);
            //  for(rint j=1;j<=n;++j)
            //  {
            //   chu("for(%lld):%lld %lld\n",j,seg.query(1,1,n,j,j).first,seg.query(1,1,n,j,j).second);
            //  }
        } else {
            pair<int, int> o = seg.query(1, 1, n, l, r);
            chu("%lld %lld\n", o.first, o.second);
        }
    }
    return 0;
}
/*
5 5
19 20
7 17
9 16
9 1
16 1
1 1 5
1 1 3
1 1 5
1 5 5
2 1 5


4 5
0 3
0 10
0 8
1 4
1 1 4
2 1 4
1 1 4
1 1 4
2 1 2
线段树
【1】mi,tag1:
支持区间+1,区间求最小值
【2】cnt1,sum1
支持单点修改,区间求和
【3】cnt2,sum2
单点修改,区间求和
s1[i][j]:x的i位置操作j次后变成的值是多少
s2[i][j]:y的
对于比暴力还劣质的线段树唯一的优化:
当mi>=20不再继续向下递归
*/

// inline int phi(int x)
// {
//     int ans=x;int bef=x;
//     for(rint i=2;i<=bef;++i)
//     {
//         if(x%i==0)
//         {
//             int u=i;
//             ans=ans*(u-1)/u;
//             while(x>1&&x%u==0)x/=u;
//         }
//         if(x==1)break;
//     }
//     if(x!=1)ans=ans*(x-1)/x;
//     return ans;
// }

T3[计数/单调栈]给出n行m列的矩形,求子矩形变成区间最大值的代价和。(n*m<=1e5)

考场

瞬间暴力想到ST表,发现200*200的部分分完全给得下,然后突然发现枚举基数\(n^4\),寄了...其实题库里是有50分的,TLEcodersT了1个点。想到要拍扁矩形统计以点为mx的贡献,发现不会。

正解

有2个切入点,枚举min(n,m)的话,n ^ 2*m是可以接受的【1】那就枚举少的n^ 2,这样确定了一个范围,立体的mx和sum就变成一个点了,直接预处理。【2】考虑区间mx的贡献,肯定是单调栈,首先肯定是无脑拍扁。

然后假设框定了l,r,sum和mx维护“点”的点权值和点和值,\(dev=-子序列的权值和+子序列max*区间长度*(r-l+1)\)

第一部分考虑每个点在子区间出现的次数:\(i*(m-i+1)\)

第二部分先用单调栈统计数字作用区间长度L,R:长度考虑左边长度和*包含右边区间个数+右边同+中间的数单独考虑,就是\(L*(L+1)/2*(R+1)+R*(R+1)/2*(L+1)+(R+1)*(L+1)\)

一些细节:空间开不下可以map,sum和mx直接随区间移动动态维护,不用预处理(否则会RE)

关于以上这些东西我想了半天本来以为是纯暴力想水50分,结果它竟然是正解?笛卡尔树呢?

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const int N = 1e5+100;
unordered_map<int,int>a[400];
ll sum[(int)1e5+10];
int mx[(int)1e5+10];
int n,m,stk[N],top,L[N],R[N];
inline ll calc(int l,int r)
{
    ll res= 1ll*l*(l+1)/2*(r+1)+1ll*r*(r+1)/2*(l+1)+1ll*(r+1)*(l+1);
   // chu("%d %d is :%lld\n",l,r,res);
    return res;
}
int main() 
{
    // freopen("1.in","r",stdin);
    // freopen("2.out","w",stdout);
   freopen("building.in", "r", stdin);
    freopen("building.out", "w", stdout);
    n=re(),m=re();
    bool fan=0;
    if(n>m)swap(n,m),fan=1;
    if(fan)
    {
        for(rint i=1;i<=m;++i)
        {
            for(rint j=1;j<=n;++j)
            {
                a[j][i]=re();
            }
        }
    }
    else
    {
        for(rint i=1;i<=n;++i)
            for(rint j=1;j<=m;++j)a[i][j]=re();
    }
    ll ans=0;
    for(rint l=1;l<=n;++l)
    {
        for(rint i=1;i<=m;++i)mx[i]=0,sum[i]=0;
        for(rint r=l;r<=n;++r)
        {
            for(rint i=1;i<=m;++i)mx[i]=max(mx[i],a[r][i]),sum[i]+=a[r][i];
            ll dev=0;
            top=0;
            for(rint i=1;i<=m;++i)L[i]=1,R[i]=m;
            for(rint i=1;i<=m;++i)//ele[l][r][what]
            {
                while(top&&mx[stk[top]]<=mx[i])
                {
                    R[stk[top--]]=i-1;
                }
                L[i]=stk[top]+1;
                stk[++top]=i;
            }
            for(rint i=1;i<=m;++i)
            dev-=sum[i]*(i)*(m-i+1);//chu("%lld * %d* %d\n",sum[l][r][i],i,m-i+1);
            stk[top+1]=m+1;
           // chu("last dev:%lld\n",dev);
            for(rint i=1;i<=m;++i)
            {
               // chu()
              // chu("L[%d]:%d R:%d\n",i,L[i],R[i]);
                dev+=calc(i-L[i],R[i]-i)*(r-l+1)*mx[i];
            }
            ans+=dev;
            //chu("for:%d--%d dev is :%lld\n",l,r,dev);
        }
    }
    chu("%lld",ans);
    return 0;
}
/*
预处理mx[l][r][c]:横着是[l,r]竖着只考虑c列的mx
sum需要统一求
2 2
1 2 3 4
4 3
233 666 555
333 333 444
444 555 444
233 332 233


3 4
2 3 1 4
5 8 1 3
2 4 3 8

,chu("for pos :%d is :%lld(%d %d %d) * %d * %d\n",i,sum[l][r][i],l,r,i,(i),(m-i+1));
            chu("only for the subsequence:%lld\n",-dev);
*/

posted on 2022-11-22 20:17  HZOI-曹蓉  阅读(28)  评论(0编辑  收藏  举报