机房模拟赛 2017年9月26日 数据结构

来源:互联网 发布:wind软件 编辑:程序博客网 时间:2024/05/22 06:28

加帕里的聚会

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

3 4
1 2 1
2 1
3 2
2 2 1 4
1 3 3 -1
2 2 2 2
3 1 3

japari1.out

SUGOI

japari2.in

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

japari2.out

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可能为负数。

就是裸树剖,真是调到吐血

#include <iostream>#include <cstring>#include <cstdio>using namespace std;const int MAXN = 100000 * 2;int n, m, a[ MAXN + 20 ], prime[ 2000000 + 20 ], head[ MAXN + 20 ], sz[ MAXN + 20 ], son[ MAXN + 20 ], tail, timer, dfn[ MAXN + 20 ], tid[ MAXN + 20 ], fa[ MAXN + 20 ], dp[ MAXN + 20 ], top[ MAXN + 20 ];bool is_not_prime[ 10000000 + 20 ];int opt;struct Tree{ int sum, mark, add; }tree[  MAXN * 4 +20 ];struct Line{ int to, nxt; }line[ MAXN * 2 + 20 ];void add_line( int from, int to ) { line[++tail].to = to; line[tail].nxt = head[from]; head[from] = tail; }void dfs1( int u, int fat, int dep ) {    dp[u] = dep;    fa[u] = fat;    sz[u] = 1;    for( register int i = head[u]; i ; i = line[i].nxt ) {        int v = line[i].to;        if( v == fat ) continue;        dfs1( v, u, dep + 1 );        sz[u] += sz[v];        if( son[u] == -1 || sz[son[u]] < sz[v] ) son[u] = v;    }}void dfs2( int u, int tp ) {    top[u] = tp;    dfn[u] = ++timer;    tid[timer] = u;    if( son[u] == -1 ) return;    dfs2( son[u], tp );    for( register int i = head[u]; i ; i = line[i].nxt ) {        int v = line[i].to;        if( v == fa[u] || v == son[u] ) continue;        dfs2( v, v );    }}//*********************DFS*************************//inline void get_ss( ) {    for( register int i = 2; i <= 10000000; i++ ) {        if( !is_not_prime[i] ) prime[ ++prime[0] ] = i;        for( register int j = 1; j <= prime[0] && i * prime[j] <= 10000000; j++ ) {            is_not_prime[ i * prime[j] ] = true;            if( i % prime[j] == 0 ) break;        }    }}//*********************线筛*************************//void pushup( int rt ) { tree[rt].sum = tree[ rt << 1 ].sum + tree[ rt << 1 | 1 ].sum; }void pushdown( int l, int r, int rt ) {    int mid = ( l + r ) >> 1;    if( tree[rt].mark != -1) {        tree[ rt << 1 ].mark = tree[rt].mark;        tree[ rt << 1 | 1 ].mark = tree[rt].mark;        tree[ rt << 1 ].sum = ( mid - l + 1 ) * tree[rt].mark;        tree[ rt << 1 | 1 ].sum = ( r - mid ) * tree[rt].mark;        tree[rt].mark = -1;        tree[ rt << 1 ].add = tree[ rt << 1 | 1 ].add = 0;    }    if( tree[rt].add != 0 ) {        tree[ rt << 1 ].add += tree[rt].add;        tree[ rt << 1 | 1 ].add += tree[rt].add;        tree[ rt << 1 ].sum += ( mid - l + 1 ) * tree[rt].add;        tree[ rt << 1 | 1 ].sum += ( r - mid ) * tree[rt].add;        tree[rt].add = 0;    }}void build( int l, int r, int rt ) {    tree[rt].mark = -1; tree[rt].add = 0;    if( l == r ) { tree[rt].sum = a[ tid[l] ]; return; }    int mid = ( l + r ) >> 1;    build( l, mid, rt << 1 );    build( mid + 1, r, rt << 1 | 1 );    pushup( rt );}void modifyadd( int L, int R, int add, int l, int r, int rt ) {    if( L <= l && R >= r ) {        tree[rt].add += add;        tree[rt].sum += ( r - l + 1 ) * add;        return;    }    int mid = ( l + r ) >> 1;    pushdown( l, r, rt );    if( L <= mid ) modifyadd( L, R, add, l, mid, rt << 1 );    if( R >  mid ) modifyadd( L, R, add, mid + 1, r, rt << 1 | 1 );    pushup( rt );}void modifyre( int L, int R, int mark, int l, int r, int rt ) {    if( L <= l && R >= r ) {        tree[rt].add = 0;        tree[rt].mark = mark;        tree[rt].sum = ( r - l + 1 ) * mark;        return;    }    pushdown( l, r, rt );    int mid = ( l + r ) >> 1 ;    if( L <= mid ) modifyre( L, R, mark, l, mid, rt << 1 );    if( R >  mid ) modifyre( L, R, mark, mid + 1, r, rt << 1 | 1 );    pushup( rt );}int query( int L, int R, int l, int r, int rt ) {    if( L <= l && R >= r ) { return tree[rt].sum; }    int mid = ( l + r ) >> 1;    pushdown( l, r, rt );    int tmp = 0;    if( L <= mid ) tmp += query( L, R, l, mid, rt << 1 );    if( R >  mid ) tmp += query( L, R, mid + 1, r, rt << 1 | 1 );    return tmp;}//*********************线段树*************************//void modifyadd( int x, int y, int v ) {    while( top[x] != top[y] ) {        if( dp[top[x]] < dp[top[y]] ) swap( x, y );        modifyadd( dfn[top[x]], dfn[x], v, 1, n, 1 );        x = fa[top[x]];    }    if( dp[x] < dp[y] ) swap( x, y );    modifyadd( dfn[y], dfn[x], v, 1, n, 1 );}void modifyre( int x, int y, int v ) {     while( top[x] != top[y] ) {        if( dp[top[x]] < dp[top[y]] ) swap( x, y );        modifyre( dfn[top[x]], dfn[x], v, 1, n, 1 );        x = fa[top[x]];     }     if( dp[x] < dp[y] ) swap( x, y );     modifyre( dfn[y], dfn[x], v, 1, n, 1 );}void query( int x, int y ) {    int sum = 0;    while( top[x] != top[y] ) {        if( dp[top[x]] < dp[top[y]] ) swap( x, y );        sum += query( dfn[top[x]], dfn[x], 1, n, 1 );        x = fa[top[x]];    }    if( dp[x] < dp[y] ) swap( x, y );    sum += query( dfn[y], dfn[x], 1, n, 1 );    if( sum <= 3 ) { printf( "TANOSHI\n" ); return; }    if( sum % 2 == 0 ) { printf( "SUGOI\n" ); return; }    int tmp = sum - 2;    if( is_not_prime[tmp] ) { printf( "TANOSHI\n" ); return; }    else                    { printf( "SUGOI\n" );   return; }}//*********************树链剖分*************************//int main( ) {    freopen( "japari.in", "r", stdin );    freopen( "japari.out", "w", stdout );    get_ss( );    scanf( "%d%d", &n, &m ); memset( son, -1, sizeof(son) );    for( register int i = 1; i <= n; i++ ) scanf( "%d", &a[i] );    for( register int i = 1; i <= n - 1; i++ ) { int ff, tt;        scanf( "%d%d", &ff, &tt );        add_line( ff, tt ); add_line( tt, ff );    }    dfs1( 1, 0, 0 );    dfs2( 1, 1 );    build( 1, n, 1 );    for( register int i = 1; i <= m; i++ ) {        scanf( "%d", &opt ); int x, y, v;         if( opt == 1 ) {            scanf( "%d%d%d", &x, &y, &v );            modifyadd( x, y, v );        } else if( opt == 2 ) {            scanf( "%d%d%d", &x, &y, &v );            modifyre( x, y, v );        } else {            scanf( "%d%d", &x, &y );            query( x, y );        }    }    return 0;}

黑白熊的密码

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

4 2
1 2 3 4
3 3 3 3

monokuma.out

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。

BZOJ 原题,一点原创性都没有,XGG naive


题解:

就是一个并查集操作,倍增思想,先把大区间放到一个集合里面,然后每次再pushdown,可以减少很多重复操作,保证复杂度是n带一个log的而不是带一个n的
这题还有点意思


#include<cstdio>#include<cctype>#include<cstring>#include<algorithm>using namespace std;long long p;const long long mod = 998244353LL;const int MAXN = 100000;int fa[20][ MAXN * 2 ], pow[ MAXN * 2 ], n, m, L, R, l, r, len;long long fast_pow( long long x, long long y ) {    long long BASE = x, tmp = 1;    while( y ) {        if( y & 1 ) tmp = ( tmp * BASE ) % mod;        BASE = BASE * BASE % mod;        y >>= 1;     }    return tmp;}int find( int M, int x ) {    if( fa[M][x] == x ) return x;    else                return fa[M][x] = find( M, fa[M][x] );}void Union( int M, int x, int y ) {    int fax = find( M, x ), fay = find( M, y );    if( fax != fay ) fa[M][fax] = fa[M][fay];} void exgcd( long long a, long long b, long long &d, long long &x, long long &y ) {    if( b == 0 ) { d = a; x = 1; y = 0; return ; }    else { exgcd( b, a % b, d, y, x ); y -= x * ( a / b ); }} long long inverse( long long a, long long b ) {    long long x, y, d;    exgcd( a, b, d, x, y );    return ( x % mod + mod ) % mod;} int main( ) {    freopen( "monokuma.in", "r", stdin );    freopen( "monokuma.out", "w", stdout );    scanf( "%d", &n );    for( register int i = 2; i <= n; i++ ) pow[i] = pow[ i >> 1 ] + 1;     for( register int i = pow[n]; i >= 0 ; i-- )  for( register int j = 1; j <= n; j++ ) fa[i][j] = j;    scanf( "%d", &m );    while( m-- ) {        scanf( "%d%d%d%d", &L, &R, &l, &r );        len = R - L + 1; int M = pow[len];        Union( M, L, l );        Union( M, R - ( 1 << M ) + 1, r - ( 1 << M) + 1 );    }    for( register int j = pow[n]; j; j-- ) {        for( register int i = 1; i + ( 1 << j ) - 1 <= n; i++ ) {            int fat = find( j, i );            Union( j - 1, i, fat );            Union( j - 1, i + ( 1 << ( j - 1 ) ), fat + ( 1 << ( j - 1 ) ) );        }    }    for( register int i = 1; i <= n; i++ )        if( find( 0, i ) == i ) p++;    long long ans; long long tmp = 9LL;    ans = tmp * fast_pow( 10LL, p - 1 ) % mod;    printf( "%I64d\n", inverse( ans, mod ) );    return 0;}

初音未来的巡游

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

【题目描述】

Miku决定在n个地点中选择一些地点进行巡游。这n个地点由n-1条道路连接,两两之间有且仅有一条路径可以互相到达。Miku希望在这些道路中选择一些放上葱,使得Miku可以选择一种方式只经过有葱的道路而巡游完所有她选择的地点(一条道路可以被多次经过,起点任选)。
Miku想知道她至少需要准备多少葱。由于她的巡游计划可能更改,她希望你能在更改后即时回答她的疑问。

【输入格式】

第一行两个整数n,m,表示地点数和事件数。
第2至n行,每行两个整数x,y,表示地点x和地点y之间有一条无向道路。
接下来一行n个0/1数,若第i个数为1则表示i号地点初始时被选,为0则表示不选。
接下来一行m个整数,依次表示修改事件。第i个数Ai表示Ai号地点的状态发生变化,即若当前被选则改为不选,当前不选则改为被选。

【输出格式】

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

【样例数据】

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,m≤3000。
对于另30%的数据,开始时所有地点都不选,保证修改操作的地点当前是不选状态。
对于100%的数据,1≤n,m≤200000,1≤x,y,Ai≤n。


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


代码不想写了,八十分代码

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <set>#include <vector>using namespace std;const int maxn = 222222;int dis[maxn], dfn[maxn], fa[maxn][20];vector<int> vec[maxn];int rdfn[maxn];int n, m;int tot = 0;set<int> st;typedef long long ll;void dfs(int u, int f) {    dfn[u] = ++tot, fa[u][0] = f; rdfn[dfn[u]] = u;    for (int i = 0; i < vec[u].size(); i++) {        int v = vec[u][i];        if (v == f) continue;        dis[v] = dis[u]+1;        dfs(v, u);    }}void init() {    for (int i = 1; i <= n; i++) {        for (int j = 1; j < 20; j++) {            fa[i][j] = fa[fa[i][j-1]][j-1];        }    }}int lca(int u, int v) {    if (dis[u] < dis[v]) swap(u, v);    if (dis[u] > dis[v]) {        int t = dis[u]-dis[v];        for (int i = 0; i < 20; i++) {            if ((t>>i)&1) u = fa[u][i];        }    }    if (u == v) return u;    for (int i = 19; i >= 0; i--) {        if (fa[u][i] != fa[v][i])            u = fa[u][i], v = fa[v][i];    }    return fa[u][0];}int query(int u, int v) {    if (u == 0 || v == 0) return 0;    return dis[u] + dis[v] - 2*dis[lca(u,v)];}int selected[maxn];int pre(int x) {    set<int>::iterator it = st.lower_bound(x);    if (it == st.begin()) return 0;    return *(--it);}int nex(int x) {    set<int>::iterator it = st.lower_bound(x);    if ((*it) > x) return *it;    if (it == st.end()) return 0;    return *(++it);}ll ans = 0;int main() {    freopen("cruise.in", "r", stdin);    freopen("cruise.out", "w", stdout);    scanf("%d%d", &n, &m);    for (int i = 1; i < n; i++) {        int u, v;        scanf("%d%d", &u, &v);        vec[u].push_back(v), vec[v].push_back(u);    }    for (int i = 1; i <= n; i++) {        scanf("%d", &selected[i]);    }    dfs(1, 0);    init();    for (int i = 1; i <= n; i++) {        if (selected[i]) {            int a = pre(dfn[i]), b = nex(dfn[i]);            ans -= query(rdfn[a], rdfn[b]);            ans += query(rdfn[a], i) + query(i, rdfn[b]);            st.insert(dfn[i]);        }    }    for (int i = 1; i <= m; i++) {        int t = 0;        scanf("%d", &t);        if (selected[t]) {            int a = pre(dfn[t]), b = nex(dfn[t]);            ans += query(rdfn[a], rdfn[b]);            ans -= query(rdfn[a], t) + query(t, rdfn[b]);            st.erase(dfn[t]);        } else {            int a = pre(dfn[t]), b = nex(dfn[t]);            ans -= query(rdfn[a], rdfn[b]);            ans += query(rdfn[a], t) + query(t, rdfn[b]);            st.insert(dfn[t]);        }        selected[t] ^= 1;        ll tmp = ans;        if (st.size() > 1) {            set<int>::iterator b = st.begin(), e = st.end();            -- e;            tmp += query(rdfn[*b], rdfn[*e]);        }        printf("%lld\n", tmp/2);    }    return 0;}
原创粉丝点击