7.26 Test——DS2

T1:

加帕里的聚会

256MB / 1s ; japari.cpp / c / pas / in / out

【题目描述】

加帕里公园里有n个区域,n-1条道路将它们连接到了一起,形成了一个树的结构。开始时,第i个区域有Ai个friends,但是由于砂之星的作用,有时从x区域到y区域的简单路径上的所有区域的friends数量都会增加v,有时从x区域到y区域的简单路径上所有区域的friends数量都会变成v。

有时,从x区域到y区域的简单路径上所有区域的friends想要聚会,聚会时需要从所有参加聚会的friends中选择一部分作为staff。加帕里的friends都很喜欢质数,因此她们希望staff和非staff的参与者的数量都是质数。

请你告诉她们,每次聚会是否存在一种方案满足她们的要求。

 

【输入格式】

第一行两个整数n,m,表示friends数量和事件数量。

接下来一行n个数,第i个数表示Ai。

接下来n-1行,每行2个数x,y,表示x和y之间有一条无向道路。

接下来m行,每行第一个整数opt表示事件类型。

若opt=1,接下来三个整数x,y,v,表示从x区域到y区域的简单路径上的所有区域的friends数量都增加v。

若opt=2,接下来三个整数x,y,v,表示从x区域到y区域的简单路径上的所有区域的friends数量都变为v。

若opt=3,接下来两个整数x,y,表示从x区域到y区域的简单路径上的所有区域的friends进行聚会。

 

【输出格式】

对于每个3事件,若可以让staff和非staff的数量都为质数,输出一行SUGOI,否则输出一行TANOSHI。

 

【样例数据】

japari1.in

japari1.out

3 4

1 2 1

2 1

3 2

2 2 1 4

1 3 3 -1

2 2 2 2

3 1 3

SUGOI

japari2.in

japari2.out

4 6

2 4 0 0

2 1

3 2

4 3

2 1 4 2

1 4 4 9

1 3 2 -2

2 4 2 5

3 1 4

3 4 4

TANOSHI

SUGOI

 

【样例解释】

在第一个样例中,三个区域的friends数量依次为4、2、0,询问的friends和为6,可以分成两组,每组的friends数量都为3。

在第二个样例中,四个区域的friends数量依次为2、5、5、5,第一个询问的friends和为17,无法分成两组。第二个询问的friends和为5,可以分为两组,每组的friends数量分别为2、3。

 

【数据范围】

对于30%的数据,n,m≤5000。

对于另30%的数据,对于i>1,第i个区域与第i-1个区域直接相连。

对于100%的数据,1≤n,m≤100000,1≤x,y≤n,一直满足0≤Ai,S≤10000000。在增加事件中v可能为负数。

 

解析:

  签到题,标准树剖, 但我写挂了,一看代码,发现我没有统计子树大小,相当于我得到了一个普通的dfs序,这才几个月没打树剖就出错了

 代码:

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005, inf = (1<<30);

template<class T>
inline void read(T &re)
{
    T f=1;char c;
    while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1;
    re=c-'0';
    while((c=getchar())&&c>='0'&&c<='9')re = (re<<3) + (re<<1) + c - '0';
    re*=f; 
}

int n, m, a[maxn], ednum;

vector<int> G[maxn];

int root, tot;
struct seg_tree{
    int ls, rs;
    int sum, cov, add;
}tr[maxn<<2];

int dep[maxn], top[maxn], pos[maxn], f[maxn], son[maxn], d[maxn], siz[maxn], cnt;

int pri[maxn*10], num;
bool prime[maxn*100];

void Euler()
{
    for(int i = 2; i <= 10000000; ++i)
    {
        if(!prime[i])
            pri[++num] = i;
        for(int j = 1; j <= num; ++j)
        {
            if(i * pri[j] > 10000000)    break;
            prime[i * pri[j]] = 1;
            if(i % pri[j] == 0)    break;
        }
    }
}

void dfs1(int x)
{
    siz[x] = 1;
    for(unsigned int i = 0; i < G[x].size(); ++i)
    {
        int id = G[x][i];
        if(id == f[x])    continue;
        dep[id] = dep[x] + 1;
        f[id] = x;
        dfs1(id);
        if(siz[id] > siz[son[x]])
            son[x] = id;
        siz[x] += siz[id];
    }
}

void dfs2(int x)
{
    pos[x] = ++cnt;
    d[cnt] = a[x];
    if(son[f[x]] == x)
        top[x] = top[f[x]];
    else
        top[x] = x;
    if(son[x])
        dfs2(son[x]);
    for(unsigned int i = 0; i < G[x].size(); ++i)
    {
        int id = G[x][i];
        if(id == f[x] || id == son[x])    continue;
        dfs2(id);
    }
}

void update(int x)
{
    int l = tr[x].ls, r = tr[x].rs;
    tr[x].sum = tr[l].sum + tr[r].sum;
}

void build(int &x, int l, int r)
{
    x = ++tot;
    tr[x].cov = -inf;
    if(l == r)
    {
        tr[x].sum = d[l];
        return ;
    }
    int mid = (l + r)>>1;
    build(tr[x].ls, l, mid);
    build(tr[x].rs, mid + 1, r);
    update(x);
}

void spread(int x, int L, int mid, int R)
{
    int l = tr[x].ls, r = tr[x].rs;
    if(tr[x].cov != -inf)
    {
        tr[l].sum = (mid - L + 1) * tr[x].cov;
        tr[r].sum = (R - mid) * tr[x].cov;
        tr[l].cov = tr[r].cov = tr[x].cov;
        tr[l].add = tr[r].add = 0;
        tr[x].cov = -inf;
    }
    if(tr[x].add)
    {
        tr[l].sum += (mid - L + 1) * tr[x].add;
        tr[r].sum += (R - mid) * tr[x].add;
        tr[l].add += tr[x].add;
        tr[r].add += tr[x].add;
        tr[x].add = 0;
    }
}

void Add(int x, int l, int r, int L, int R, int v)
{
    if(l <= L && R <= r)
    {
        tr[x].sum += v * (R - L + 1);
        tr[x].add += v;
        return ;
    }
    int mid = (L + R)>>1;
    spread(x, L, mid, R);
    if(l <= mid)
        Add(tr[x].ls, l, r, L, mid, v);
    if(mid < r)
        Add(tr[x].rs, l, r, mid + 1, R, v);
    update(x);
}

void Cover(int x, int l, int r, int L, int R, int v)
{
    if(l <= L && R <= r)
    {
        tr[x].sum = v * (R - L + 1);
        tr[x].cov = v;
        tr[x].add = 0;
        return ;
    }
    int mid = (L + R)>>1;
    spread(x, L, mid, R);
    if(l <= mid)
        Cover(tr[x].ls, l, r, L, mid, v);
    if(mid < r)
        Cover(tr[x].rs, l, r, mid + 1, R, v);
    update(x);
}

int Query(int x, int l, int r, int L, int R)
{
    if(l <= L && R <= r)
        return tr[x].sum;
    int mid = (L + R)>>1;
    spread(x, L, mid, R);
    int ret = 0;
    if(l <= mid)
        ret += Query(tr[x].ls, l, r, L, mid);
    if(mid < r)
        ret += Query(tr[x].rs, l, r, mid + 1, R);
    update(x);
    return ret;
}

void work1(int u, int v, int x)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] > dep[top[v]])
        {
            Add(root, pos[top[u]], pos[u], 1, n, x);
            u = f[top[u]];
        }
        else
        {
            Add(root, pos[top[v]], pos[v], 1, n, x);
            v = f[top[v]];
        }
    }
    if(dep[u] < dep[v])
        Add(root, pos[u], pos[v], 1, n, x);
    else
        Add(root, pos[v], pos[u], 1, n, x);
}

void work2(int u, int v, int x)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] > dep[top[v]])
        {
            Cover(root, pos[top[u]], pos[u], 1, n, x);
            u = f[top[u]];
        }
        else
        {
            Cover(root, pos[top[v]], pos[v], 1, n, x);
            v = f[top[v]];
        }
    }
    if(dep[u] < dep[v])
        Cover(root, pos[u], pos[v], 1, n, x);
    else
        Cover(root, pos[v], pos[u], 1, n, x);
}

int work3(int u, int v)
{
    int ret = 0;
    while(top[u] != top[v])
    {
        if(dep[top[u]] > dep[top[v]])
        {
            ret += Query(root, pos[top[u]], pos[u], 1, n);
            u = f[top[u]];
        }
        else
        {
            ret += Query(root, pos[top[v]], pos[v], 1, n);
            v = f[top[v]];
        }
    }
    if(dep[u] < dep[v])
        ret += Query(root, pos[u], pos[v], 1, n);
    else
        ret += Query(root, pos[v], pos[u], 1, n);
    return ret;
}

bool check(int x)
{
    if(x <= 3)    return 0;
    if(!(x&1))    return 1;
    x -= 2;
    return !prime[x];
}

int main()
{
    freopen("japari.in", "r", stdin);
    freopen("japari.out", "w", stdout);
    Euler();
    read(n);read(m);
    for(int i = 1; i <= n; ++i)
        read(a[i]);
    for(int i = 1; i < n; ++i)
    {
        int x, y;
        read(x);read(y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    dfs1(1);
    //cout<<"hahahahaha"<<endl;
    dfs2(1);
    build(root, 1, n);
    for(int i = 1; i <= m; ++i)
    {
        int opt, u, v;
        read(opt);read(u);read(v);
        if(opt == 1)
        {
            int x;
            read(x);
            work1(u, v, x);
        }
        else if(opt == 2)
        {
            int x;
            read(x);
            work2(u, v, x);
        }
        else
        {
            int res = work3(u, v);
            printf("%s\n", check(res)? "SUGOI": "TANOSHI");
        }
        
    }
    return 0;
}
japari

 

 

T2:

  

黑白熊的密码

 

256MB / 1s ; monokuma.cpp / c / pas / in / out

 

【题目描述】

 

苗木诚发现了脱出装置,它可以帮助所有人逃脱希望之峰学园。但是要想使用它,必须输入一个十进制的n位数密码(没有前导0)。为了让游戏变得更加有趣,黑白熊提供了m条信息,其中第i条信息指出这个数的第Li位到Ri位和第li位到ri位完全相同。

 

苗木诚决定随机选择一个满足要求的n位数输入。请你告诉他他碰对密码的概率是多少。

 

 

 

【输入格式】

 

第一行两个整数n,m,表示数的位数和黑白熊提供的信息数。

 

接下来m行,每行四个正整数Li,Ri,li,ri。

 

 

 

【输出格式】

 

输出一行一个整数,表示苗木诚碰对密码的概率,对998244353取模。

 

 

 

【样例数据】

 

monokuma.in

monokuma.out

4 2

1 2 3 4

3 3 3 3

144190851

 

 

 

【样例解释】

 

答案是1/90在模998244353意义下的值。

 

 

 

【数据范围】

 

对于30%的数据,n,m≤2000。

 

对于100%的数据,1≤n,m≤100000,1≤Li≤Ri≤n,1≤li≤ri≤n,且保证Ri-Li=ri-li。

解析:

  原题:Bzoj4569: [Scoi2016]萌萌哒

  并查集好题, 考试的时候我只写了暴力维护每个点的并查集的算法, 30pts。1e5的数据范围显然需要一个log的优化, 以方便快速地维护并查集,发现在单点维护并查集时,许多点都被访问了多次,这样很不优,于是在并查集中维护连续的点,即一段区间,在最后把标记下放给小一级的区间,最终可以下放到单点, 区间长度与在st表中一样, 都是2k,这样就做到了O(nlogn)的时间复杂度

 代价:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
const ll mod = 998244353;

template<class T>
inline void read(T &re)
{
    T f=1;char c;
    while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1;
    re=c-'0';
    while((c=getchar())&&c>='0'&&c<='9')re = (re<<3) + (re<<1) + c - '0';
    re*=f; 
}

int n, m, tot, f[maxn*20], res, pw[20], timer, mp[maxn*20], id[20][maxn];
ll ans = 1;

ll qpow(ll x, ll y)
{
    ll ret = 1;
    while(y)
    {
        if(y&1)
            ret = ret * x % mod;
        x = x * x % mod;
        y>>=1;
    }
    return ret;
}

int Find(int x)
{
    return x == f[x]? x: f[x] = Find(f[x]);
}

int main()
{
    freopen("monokuma.in", "r", stdin);
    freopen("monokuma.out", "w", stdout);
    read(n);read(m);
    pw[0] = 1;
    for(int i = 1; i <= 18; ++i)
        pw[i] = pw[i-1] * 2;
    for(int i = 1; i <= n; ++i)
        for(int j = 0; j <= 18 && i + pw[j] - 1 <= n; ++j)
        {
            id[j][i] = ++timer;
            mp[timer] = i;
            f[timer] = timer;
        }
    for(int i = 1; i <= m; ++i)
    {
        int l1, r1, l2, r2;
        read(l1);read(r1);read(l2);read(r2);
        for(int j = 18; j >= 0; --j)
            if(l1 + pw[j] - 1 <= r1)
            {
                f[Find(id[j][l1])] = Find(id[j][l2]);
                l1 += pw[j];
                l2 += pw[j];
            }
    }
    for(int j = 18; j; --j)
        for(int i = 1; i + pw[j] - 1 <= n; ++i)
        {
            int k = Find(id[j][i]), s = mp[k];
            f[Find(id[j-1][i])] = Find(id[j-1][s]);
            f[Find(id[j-1][i+pw[j-1]])] = Find(id[j-1][s+pw[j-1]]);
        }
    for(int i = 1; i <= n; ++i)
        if(Find(id[0][i]) == id[0][i])
            res++;
    ans = 1LL * 9;
    ans = ans * qpow(1LL * 10, 1LL * (res - 1)) % mod;
    printf("%lld", qpow(ans, mod - 2));
    return 0;
}
monokuma

 

T3:

初音未来的巡游

 

128MB / 1s ; cruise.cpp / c / pas / in / out

 

【题目描述】

 

Miku决定在n个地点中选择一些地点进行巡游。这n个地点由n-1条道路连接,两两之间有且仅有一条路径可以互相到达。Miku希望在这些道路中选择一些放上葱,使得Miku可以选择一种方式只经过有葱的道路而巡游完所有她选择的地点(一条道路可以被多次经过,起点任选)。

 

Miku想知道她至少需要准备多少葱。由于她的巡游计划可能更改,她希望你能在更改后即时回答她的疑问。

 

 

 

【输入格式】

 

第一行两个整数n,m,表示地点数和事件数。

 

2n行,每行两个整数x,y,表示地点x和地点y之间有一条无向道路。

 

接下来一行n0/1数,若第i个数为1则表示i号地点初始时被选,为0则表示不选。

 

接下来一行m个整数,依次表示修改事件。第i个数Ai表示Ai号地点的状态发生变化,即若当前被选则改为不选,当前不选则改为被选。

 

 

 

【输出格式】

 

输出m行,第i行一个整数表示第i次事件后初音最少需要准备多少葱。

 

 

 

【样例数据】

 

cruise.in

cruise.out

5 8

1 2

1 3

2 4

2 5

1 0 0 1 0

5 4 2 1 2 5 3 2

3

2

2

1

0

0

0

2

 

 

 

【数据范围】

对于30%的数据,n,m3000对于另30%的数据,开始时所有地点都不选,保证修改操作的地点当前是不选状态。

 

对于100%的数据,1n,m2000001x,y,Ain

 

 

解析:

   没搞出来, 先CV一波Solution

可以证明答案的两倍为按DFS序排序后相邻两个选择点的距离和加上首尾点的距离,则预处理LCA,使用set维护选择的点,进行修改时根据DFS序的前驱和后继点更新答案即可。

复杂度O(mlogn + nlogn)。

 代码鸽了

 

 

 

 

 

 

转载于:https://www.cnblogs.com/Joker-Yza/p/11253108.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值