BC Round 74

LCP Array

 
 Time Limit: 4000/2000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
Peter有一个字符串s=s_{1}s_{2}...s_{n}s=s1s2...sn, 令\text{suff}_i =s_{i}s_{i+1}...s_{n}suffi=sisi+1...snssii字符开头的后缀. Peter知道任意两个相邻的后缀的最长公共前缀a_i = \text{lcp}(\text{suff}_i, \text{suff}_{i+1}) \quad (1 \le i < nai=lcp(suffi,suffi+1)(1i<n).

现在给你数组aa, Peter有多少个仅包含小写字母的字符串满足这个数组. 答案也许会很大, 你只要输出对10^9 + 7109+7取模的结果即可.
输入描述
输入包含多组数据. 第一行有一个整数TT, 表示测试数据的组数. 对于每组数据:

第一行包含一个整数nn (2 \le n \le 10^5)2n105)表示字符串的长度. 第二行包含n - 1n1个整数: a_1,a_2,...,a_{n-1}a1,a2,...,an1 (0 \le a_i \le n)(0ain).

所有数据中nn的和不超过10^6106.
输出描述
对于每组数据, 输出答案对10^9+7109+7取模的结果.
输入样例
3
3
0 0
4
3 2 1
3
1 2
输出样例
16250
26

0

题解:

如果a_i=xai=x, 那么可以推断出s_i=s_{i+1}=...=s_{i+x}si=si+1=...=si+x, 并且如果a_i \ne 0ai0, 那么a_{i+1}=a_i-1ai+1=ai1, 利用第二个条件判断无解, 利用第一个条件划分等价类. 假设有mm个等价类, 那么答案就是26\cdot 25^{m-1}2625m1

从后面往前面算要比较简单,而且不容易出错,实际上只需要统计0的个数就行,另一个条件是判断有无解的。

#include <stdio.h>
#define LL long long
const int maxn = 100005;
int a[maxn];
LL ans, MOD = 1000000007;
int main ( )
{
    int T, n;
    scanf ( "%d", &T );
    while ( T -- )
    {
        scanf ( "%d", &n );
        for ( int i = 1; i < n; i ++ )
            scanf ( "%d", &a[i] );
        int mx = 0;
        ans = 26;
        for ( int i = n-1; i >= 1; i -- )
        {
            if ( a[i] == 0 )    //统计零的个数就行了
            {
                mx = 0;
                ans = ( ans*25 )%MOD;
                continue ;
            }
            if ( a[i] != mx+1 )
            {
                ans = 0;
                break ;
            }
            else
            {
                mx = a[i];
            }
        }
        printf ( "%I64d\n", ans );
    }
    return 0;
}

Shortest Path

 Time Limit: 4000/2000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
有一条长度为nn的链. 节点iii+1i+1之间有长度为11的边. 现在又新加了3条边, 每条边长度都是1. 给出mm个询问, 每次询问两点之间的最短路.
输入描述
输入包含多组数据. 第一行有一个整数TT, 表示测试数据的组数. 对于每组数据:

第一行包含2个整数nnmm (1 \le n,m \le 10^5)(1n,m105)表示节点的数目和询问数目. 接下来一行包含66个有空格分开的整数a_1, b_1, a_2, b_2, a_3, b_3a1,b1,a2,b2,a3,b3 (1 \le a_1,a_2,a_3,b_1,b_2,b_3 \le n)(1a1,a2,a3,b1,b2,b3n), 表示新加的三条边为(a_1,b_1)(a1,b1), (a_2,b_2)(a2,b2), (a_3,b_3)(a3,b3). 接下来mm行, 每行包含两个整数s_isit_iti (1 \le s_i, t_i \le n)(1si,tin), 表示一组询问.

所有数据中mm的和不超过10^6106.
输出描述
对于每组数据, 输出一个整数S=(\displaystyle\sum_{i=1}^{m} i \cdot z_i) \text{ mod } (10^9 + 7)S=(i=1mizi) mod (109+7), 其中z_izi表示第ii组询问的答案.
输入样例
1
10 2
2 4 5 7 8 10
1 5
3 1
输出样例
7
此题可以直接建边,相邻两个节点和新添加的边,然后将从新加边出发到各点的最短距离算出,找一个最小的就可了。

#include <stdio.h>
#include <vector>
#include <string.h>
#include <queue>
#include <algorithm>
#define LL long long
using namespace std;
const int maxn = 100005, MOD = 1e9+7;
int a[6], d[6][maxn];
bool inq[maxn];
vector < int > mp[maxn];
void init ( int n )
{
    for ( int i = 0; i <= n; i ++ )
        mp[i].clear ( );
    for ( int i = 1; i <= n; i ++ )
    {
        if ( i > 1 )    //相邻建边
        {
            mp[i].push_back ( i-1 );
            mp[i-1].push_back ( i );
        }
    }
}
void spfa ( int k, int s )
{
    memset ( d[k], 0x3f, sizeof ( d[k] ) );
    memset ( inq, false, sizeof ( inq ) );
    d[k][s] = 0;
    queue < int > q;
    q.push ( s );
    while ( ! q.empty ( ) )
    {
        int u = q.front ( );
        q.pop ( );
        inq[u] = false;
        for ( int i = 0; i < mp[u].size ( ); i ++ )
        {
            int v = mp[u][i];
            if ( d[k][v] > d[k][u]+1 )
            {
                d[k][v] = d[k][u]+1;
                if ( inq[v] == false )
                {
                    inq[v] = true;
                    q.push ( v );
                }
            }
        }
    }
}
void solve ( )
{
    int T, n, m, u, v;
    scanf ( "%d", &T );
    while ( T -- )
    {
        scanf ( "%d%d", &n, &m );
        init ( n );
        for ( int i = 0; i < 3; i ++ )
        {
            scanf ( "%d%d", &a[i*2], &a[i*2+1] );
            mp[ a[i*2] ].push_back ( a[i*2+1] );
            //新加的边建边
            mp[ a[i*2+1] ].push_back ( a[i*2] );
        }
        for ( int i = 0; i < 6; i ++ )
            spfa ( i, a[i] );
        //从a[i]这点出发到其他点的最短路
        int ans = 0;
        for ( int i = 1; i <= m; i ++ )
        {
            scanf ( "%d%d", &u, &v );
            if ( u > v )
                swap ( u, v );
            int z = v-u;
            for ( int j = 0; j < 6; j ++ )
                z = min ( z, d[j][u]+d[j][v] );
            //从a[j]出发到达uv的最短路和
            ans = ( ans+( LL )i*z )%MOD;//乘法数据超出范围
        }
        printf ( "%d\n", ans );
    }
}
int main ( )
{
    solve ( );
    return 0;
}

Transform

 Time Limit: 4000/2000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
给出nn个整数, 对于一个整数xx, 你可以做如下的操作若干次:

  + 令xx的二进制表示为\overline{b_{31}b_{30}...b_0}b31b30...b0, 你可以翻转其中一个位.
  + 令yy是给出的其中一个整数, 你可以把xx变为x \oplus yxy, 其中\oplus表示位运算里面的异或操作.

现在有若干整数对(S, T)(S,T), 对于每对整数你需要找出从SS变成TT的最小操作次数.
输入描述
输入包含多组数据. 第一行有一个整数TT (T \le 20)(T20), 表示测试数据组数. 对于每组数据:

第一行包含两个整数nnmm (1 \le n \le 15, 1 \le m \le 10^5)(1n15,1m105), 表示给出整数的数目和询问的数目. 接下来一行包含nn个用空格分隔的整数a_1, a_2, ..., a_na1,a2,...,an (1 \le a_i \le 10^5)(1ai105).

接下来mm行, 每行包含两个整数s_isit_iti (1 \le s_i, t_i \le 10^5)(1si,ti105), 代表一组询问.
输出描述
对于每组数据, 输出一个整数S=(\displaystyle\sum_{i=1}^{m} i \cdot z_i) \text{ mod } (10^9 + 7)S=(i=1mizi) mod (109+7), 其中z_izi是第ii次询问的答案.
输入样例
1
3 3
1 2 3
3 4
1 2
3 9
输出样例
10
Hint
3 \to 434 (2次操作): 3 \to 7 \to 4374

1 \to 212 (1次操作): 1 \oplus 3 = 213=2

3 \to 939 (2次操作): 3 \to 1 \to 9319
这道题因为是对位直接操作,是可以将s-t看成0-s^t就行了,因为s与t二进制为相同的地方可以不需要变化,

然后预处理0-10^5需要的步数就可了。

#include <stdio.h>
#include <string.h>
#include <utility>
#include <queue>
#define LL long long
using namespace std;
typedef pair < int, LL > P;
const int N = 15, maxn = 140005, MOD = 1e9+7;
int a[N], ans[maxn], vis[maxn], n;
void bfs ( int s )  //将0~2^17的变换次数全部算出来
{
    priority_queue < P, vector < P >, greater < P > > q;
    q.push ( P ( 0, s ) );
    vis[s] = 1;
    ans[s] = 0;
    while ( ! q.empty ( ) )
    {
        P p = q.top ( );
        q.pop ( );
        for ( int i = 0; i < n; i ++ )  //x^a[i]
        {
            LL x = p.second ^ a[i];
            if ( x >= maxn || vis[x] )
                continue ;
            vis[x] = 1;
            ans[x] = p.first+1;
            q.push ( P ( p.first+1, x ) );
        }
        for ( int i = 0; i <= 17; i ++ )    //x变某一位
        {
            LL x = p.second ^ ( 1 << i );
            if ( x >= maxn || vis[x] )
                continue ;
            vis[x] = 1;
            ans[x] = p.first+1;
            q.push ( P ( p.first+1, x ) );
        }
    }
}
void solve ( )
{
    int T, m, s, t;
    LL res;
    scanf ( "%d", &T );
    while ( T -- )
    {
        memset ( ans, -1, sizeof ( ans ) );
        memset ( vis, 0, sizeof ( vis ) );
        scanf ( "%d%d", &n, &m );
        for ( int i = 0; i < n; i ++ )
            scanf ( "%d", &a[i] );
        bfs ( 0 );
        res = 0;
        for ( int i = 1; i <= m; i ++ )
        {
            scanf ( "%d%d", &s, &t );
            //因为s-t是进行位变换,所以相同位可以不用变,就相当于从0变换到s^t的步数
            res = ( res+( LL )i*ans[s^t] )%MOD;
        }
        printf ( "%I64d\n", res );
    }
}
int main ( )
{
    solve ( );
    return 0;
}

Toposort

 Time Limit: 10000/5000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
给出nn个点mm条边的有向无环图. 要求删掉恰好kk条边使得字典序最小的拓扑序列尽可能小.
输入描述
输入包含多组数据. 第一行有一个整数TT, 表示测试数据组数. 对于每组数据:

第一行包含3个整数nn, mmkk (1 \le n \le 100000, 0 \le k \le m \le 200000)(1n100000,0km200000), 表示图中结点数目, 图中边的数目以及要删的边数.

接下来mm行, 每行包含两个整数u_iui and v_ivi, 表示存在一条u_iuiv_ivi的有向边 (1 \le u_i, v_i \le n)(1ui,vin).

输入保证给定的图是一个DAG. 输入数据中nn的和不超过10^6106. 输入数据中mm的和不超过2 \cdot 10^62106.
输出描述
对于每组数据, 输出一个整数S = (\displaystyle\sum_{i=1}^{n}{i\cdot p_i}) \text{ mod } (10^9 + 7)S=(i=1nipi) mod (109+7), 其中p_{1}, p_{2}, ..., p_{n}p1,p2,...,pn是字典序最小的那个拓扑序列.
输入样例
3
4 2 0
1 2
1 3
4 5 1
2 1
3 1
4 1
2 3
2 4
4 4 2
1 2
2 3
3 4
1 4
输出样例
30
27
30

题解:

参考下普通的用堆维护求字典序最小拓扑序, 用某种数据结构维护入度小于等于kk的所有点, 每次找出编号最小的, 并相应的减少kk即可.

这个数据结构可以用线段树, 建立一个线段树每个节点[l,r][l,r]维护编号从llrr的所有节点的最小入度, 查询的时候只需要在线段树上二分, 找到最小的xx满足入度小于等于kk.

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

用优先队列维护入度小于等于K的节点,每次push最小的点,更新与其相连的入度,继续加入。

#include <stdio.h>
#include <vector>
#include <queue>
#include <string.h>
#include <utility>
#define LL long long
using namespace std;
typedef pair < int, int > P;
const int maxn = 100005, MOD = 1e9+7;
vector < int > mp[maxn];
int d[maxn], vis[maxn];
void init ( int n )
{
    for ( int i = 0; i <= n; i ++ )
    {
        mp[i].clear ( );
        d[i] = 0;
        vis[i] = 0;
    }
}
void solve ( )
{
    int T, n, k, m, u, v;
    priority_queue < int, vector < int >, greater < int > > q;
    scanf ( "%d", &T );
    while ( T -- )
    {
        scanf ( "%d%d%d", &n, &m, &k );
        init ( n );
        while ( m -- )
        {
            scanf ( "%d%d", &u, &v );
            mp[u].push_back ( v );
            d[v] ++;
        }
        for ( int i = 1; i <= n; i ++ ) //用优先队列维护入度小于等于k
        {
            if ( d[i] <= k )
                q.push ( i );
        }
        LL ans = 0;
        for ( int i = 1; i <= n; i ++ )
        {
            int u;
            while ( true )
            {
                u = q.top ( );
                q.pop ( );
                if ( vis[u] == 0 && d[u] <= k )
                    break ;
            }
            vis[u] = 1; //标记已用
            k -= d[u];  //去掉连接u的边
            ans = ( ans+( LL )i*u )%MOD;
            for ( int j = 0; j < mp[u].size ( ); j ++ )
            {
                int v = mp[u][j];
                if ( vis[v] == 0 )
                {
                    d[v] --;
                    if ( d[v] <= k )    //小于等于k
                        q.push ( v );
                }
            }
        }
        printf ( "%I64d\n", ans );
    }
}
int main ( )
{
    solve ( );
    return 0;
}
用线段树也是一样的

#include <stdio.h>
#include <string.h>
#define INF 0x3f3f3f3f
#define lson l, m, rt << 1
#define rson m+1, r, rt << 1 | 1
#define LL long long
const int maxn = 200005, N = 100005, MOD = 1e9+7;
int head[maxn], nxt[maxn], to[maxn], pos;
int min_rd[N << 2], ans[N], k, d[N];
void addEdge ( int u, int v )
{
    to[pos] = v;
    nxt[pos] = head[u];
    head[u] = pos ++;
}
inline int Min ( int a, int b )
{
    return a < b ? a : b;
}
void PushUP ( int rt )
{
    min_rd[rt] = Min ( min_rd[rt << 1], min_rd[rt << 1 | 1] );
}
void update ( int q, int v, int l, int r, int rt )
{
    if ( l == r )
    {
        min_rd[rt] = v;
        return ;
    }
    int m = ( l+r ) >> 1;
    if ( q <= m )
        update ( q, v, lson );
    else
        update ( q, v, rson );
    PushUP ( rt );
}
int query ( int q, int l, int r, int rt )
{
    if ( l == r )
        return l;
    int m = ( l+r ) >> 1, u;
    if ( min_rd[rt << 1] <= q )
        u = query ( q, lson );
    else
        u = query ( q, rson );
    return u;
}
void solve ( )
{
    int T, n, m, u, v;
    scanf ( "%d", &T );
    while ( T -- )
    {
        pos = 0;
        memset ( head, -1, sizeof ( head ) );
        memset ( d, 0, sizeof ( d ) );
        scanf ( "%d%d%d", &n, &m, &k );
        while ( m -- )
        {
            scanf ( "%d%d", &u, &v );
            addEdge ( u, v );
            d[v] ++;
        }
        for ( int i = 1; i <= n; i ++ )
            update ( i, d[i], 1, n, 1 );//直接修改,更新对数组更新
        int cnt = 0;
        for ( int i = 1; i <= n; i ++ )
        {
            int u = query ( k, 1, n, 1 );
            ans[cnt ++] = u;
            k -= d[u];  //去掉d[u]条边
            d[u] = INF; //设为∞
            update ( u, d[u], 1, n, 1 );
            for ( int j = head[u]; ~ j; j = nxt[j] )
            {
                int v = to[j];
                d[v] --;    //入度-1
                update ( v, d[v], 1, n, 1 );
            }
        }
        /*
        for ( int i = 0; i < cnt; i ++ )
            printf ( "%d ", ans[i] );
        printf ( "\n" );
        */
        LL res = 0;
        for ( int i = 1; i <= n; i ++ )
            res = ( res+( LL )i*ans[i-1] )%MOD;
        printf ( "%I64d\n", res );
    }
}
int main ( )
{
    solve ( );
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值