HGOI20190811 省常中互测4

  Problem A magic

给出一个字符串$S$,和数字$n$,要求构造长度为$n$只含有小写字母的字符串$T$,

使得在$T$中存在删除且仅删除一个子串使得$S=T$成立。

输出$T$的构造方案数,mod 998244353的值。

对于$100 \% $的数据 $2  \leq n \leq 10^{18} , |S| \leq 10^6$

Sol : 考虑$T$合法的条件是和$S$有相同的前缀和相同的后缀,且相同前后缀长度和是$|S|$ 

     若最长公共前缀长度为$0$ ,那么说明$S$和$T$最后$|S|$位相同,合法情况$T$的取值有$26^{n - |S|}$ 种。

  若最长公共前缀长度不为$0$ ,那么说明前半部分至少有$k$是$S$的前缀,后半部分就有$|S| - k $的长度是后缀,这个时候由于倒数$|S| - k$ 个 不能和上一次一样,这个位置只有$25$种可能,其他位置是$26$种可能,这种情况下方案数时$26^{n-|S|-1}$

    最后答案就是$26^n - 26^{n-1} - |S|\times 25 \times 26^{n-|S|-1}$

  注意需要特判$n = |S|$的情况,答案就是$26 ^ n - 1$ 

 复杂度是$O(log_2 n)$

#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long n,m;
char tmp[1000100];
long long Pow(long long x,long long k)
{
    if(k<0) return 0ll; 
    if(!k) return 1ll;
    long long res=Pow(x,k/2);
    res=res*res%mod;
    if(k%2) res=res*x%mod;
    return res;
}
int main()
{
    scanf("%lld %s",&n,tmp);
    m=strlen(tmp);
    printf("%lld",(long long)(Pow(26ll,n)-(Pow(26ll,n-m)%mod+(m*25ll%mod)*Pow(26ll,n-m-1)%mod)%mod+mod)%mod);
    return 0;
}
A.cpp

   Problem B graph

  给出可重边无自环不保证连通的无向图$G$ ,询问$u $到$v$简单路径上经过边权的最大值最小。

  对于$100\% $ 的数据$n,m,q \leq 3 \times 10^5$

       Sol: 建出最小生成树(在建树过程考虑了重边了)。、

    然后用并查集维护连通性。

    最大边权最小等价于在最小生成树树上路径的最大值。

    复杂度就是$O(m log_2 m)$

# include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
map<int,int>mp[N]; 
int g[N][22],d[N][22],dep[N],fc[N],n,m,q;
vector<pair<int , int> >E[N];
struct edge{
    int u,v,w;
};
vector<edge>Edge;
bool cmp(edge a,edge b){return a.w < b.w;}
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void write(int x)
{
    if (x<0) x=-x,putchar('-');
    if (x>9) write(x/10);
    putchar('0'+x%10);
}
void writeln(int x)
{
    write(x); putchar('\n');
}
int father(int x)
{
    if (fc[x]==x) return x;
    return fc[x]=father(fc[x]);
}
void kruskal()
{
    sort(Edge.begin(),Edge.end(),cmp);
    for (int i = 1; i <= n; i++) fc[i] = i;
    for (int i = 0; i < Edge.size(); i++) {
        int u=Edge[i].u,v=Edge[i].v,w=Edge[i].w;
        int fx = father(Edge[i].u),fy = father(Edge[i].v);
        if (fx == fy) continue;
        fc[fx] = fy;
        E[u].push_back(make_pair(v,w));
        E[v].push_back(make_pair(u,w));
    } 
}
void dfs(int u,int fa)
{
    dep[u]=dep[fa]+1; g[u][0]=fa;
    for (int i=0;i<E[u].size();i++) {
        int v=E[u][i].first,w=E[u][i].second;
        if (v==fa) continue;
        d[v][0]=w;
        dfs(v,u);
    }
}
void init()
{
    for (int i = 1 ; i<=n ; i++)
        if (!dep[i]) dfs(i,0);
    for (int i = 1; i <= 21 ; i++)
     for (int j = 1; j <= n ; j++)
       g[j][i] = g[g[j][i-1]][i-1],
       d[j][i] = max(d[j][i-1] , d[g[j][i-1]][i-1]);       
}
int query(int u,int v)
{
    int fx = father(u), fy = father(v);
    if (fx != fy) return -1;
    if (dep[u] < dep[v]) swap(u,v);
    int ret = 0;
    for (int i = 21 ; i >= 0 ; i--)
     if (dep[g[u][i]] >= dep[v]) 
        ret = max(ret,d[u][i]),u=g[u][i];
    if (u == v) return ret;
    for (int i = 21 ; i >= 0 ;i--)
     if (g[u][i] != g[v][i]) 
        ret = max(max(ret , d[u][i]) , d[v][i]),
        u = g[u][i] , v = g[v][i];
    return max(max(ret,d[u][0]),d[v][0]);   
}
int main()
{
    n=read();m=read();q=read();
    for (int i=1;i<=m;i++) {
        int u=read(),v=read(),w=read();
        if (mp[u].count(v) != 0) w = min(w , mp[u][v]);
        mp[u][v] = mp[v][u] = w;
    }
    map<int,int>::iterator it;
    for (int i = 1; i <= n ; ++i ) 
        for (it = mp[i].begin() ; it != mp[i].end() ; ++it) 
            Edge.push_back((edge){i , it->first , it->second});
    kruskal(); init();
    while (q--) {
        int u=read(),v=read();
        writeln(query(u,v));
    }
    return 0;
}
B.cpp

      Problem C number

  定义不算前导零,只由两个数字构成的数为“好数”,如$101010$, $11111$

       给出$T$个询问,询问$x$至少由几个好数相加组成的(可以同一个数用多次)。

  对于$100\%$的数据$1 \leq n \leq 10^{18} ,1 \leq T \leq 100$

  Sol:考虑$01$ , $02$ , $03$ , $04$最多四个数字就可以创造世界了,所以我们只需要判断$3$及以下的情况,剩余情况就输出$4$

  我们可以枚举$C_{10}^{2} = 45$种选择数的方案$mask[i]$来构成这些数,然后用dp验证即可。

  我们只至少这若干个数加起来是多少,我们要还原他为0,才能检验是否合法。

  对于每个三个数累加的方案,我们都有这三个数中任意选2个任意选1个不选这7种子方案,也能获得相同的效果,我们特殊考虑,记录在$Sub[i]$中。

  我们可以预处理出每个个选择数的方案,可以构成哪些最小合法的数(显然这些数都是小于等于$3 \times 9 =27$的)

      设$f[i][j]$表示使用标号为$i$的方案,到达$j$数位上的信息,这个信息是一个3位二进制数,分别表示高位向低位退位为$i , i\in[0,2]$是否合法。

  转移的话可以用位运算转移。

  最后如果当前数位为1,并且可以退0位的方案是ok的,那么我们就对对应取数的个数求min即可。(如果无法构成答案就是4了)

  复杂度是$O(T \times C_{45}^{3} \times 18 \times 3 \times 3)$

# include <bits/stdc++.h>
# define int long long
using namespace std;
int mask[110],f[20010][22],s[55][55][55],val[20010],a[23];
bool ok[20010][34];
int size;
vector<int>Sub[20010];
signed main()
{
    for (int i = 0 ; i <= 9 ; i++ )
        for (int j = i + 1 ; j <= 9 ; j++ )
             mask[++mask[0]] = i * 10 + j;               
    memset(ok, false, sizeof(ok));
    int t = mask[0] ; mask[0] = 0;
    for (int i = 0 ;i <= t ; i++)
        for (int j = i ; j <= t ; j++)
            for (int k = j ; k <= t ;k++) {
                val[++size] = 3 - (i==0) - (j==0) - (k==0);
                ok[size][mask[i]/10 + mask[j]/10 + mask[k]/10] = ok[size][mask[i]%10 + mask[j]/10 + mask[k]/10] = true;
                ok[size][mask[i]/10 + mask[j]%10 + mask[k]/10] = ok[size][mask[i]/10 + mask[j]/10 + mask[k]%10] = true;
                ok[size][mask[i]%10 + mask[j]%10 + mask[k]/10] = ok[size][mask[i]%10 + mask[j]/10 + mask[k]%10] = true;
                ok[size][mask[i]/10 + mask[j]%10 + mask[k]%10] = ok[size][mask[i]%10 + mask[j]%10 + mask[k]%10] = true;
                s[i][j][k] = s[i][k][j] = s[j][i][k] = s[j][k][i] = s[k][i][j] = s[k][j][i] = size;
                if (s[0][j][k] != size) Sub[size].push_back(s[0][j][k]);
                if (s[i][0][k] != size) Sub[size].push_back(s[i][0][k]);
                if (s[i][j][0] != size) Sub[size].push_back(s[i][j][0]);
                if (s[0][0][k] != size) Sub[size].push_back(s[0][0][k]);
                if (s[0][j][0] != size) Sub[size].push_back(s[0][j][0]);
                if (s[i][0][0] != size) Sub[size].push_back(s[i][0][0]);
                if (s[0][0][0] != size) Sub[size].push_back(s[0][0][0]);
            }               
    int T; scanf("%lld",&T);
    while (T--) {
        memset(f, 0 , sizeof(f));
        int x; scanf("%lld",&x); a[0] = 0;
        while (x) { a[++a[0]] = x%10; x/=10;}
        for (int i = 1; i<= size; i++) f[i][a[0]+1] = 1;
        for (int i = a[0] ; i>=1 ; i--) {
            for (int j = 1; j <= size ; j++) {
                for (int last = 0 ; last <= 2 ; last++) if (f[j][i+1] & (1<<last)) 
                    for (int to = 0; to <= 2 ; to++) if (last*10 + a[i] - to >= 0 && ok[j][last*10 + a[i] - to]) {
                        f[j][i] |= (1<<to) ;
                    }
                for (int k = 0 ;k < Sub[j].size() ; k++) {
                        int tmp = Sub[j][k] ;   
                        f[j][i] |= f[tmp][i];
                }    
            }
        }
        int ans = 4;
        for (int i = 1 ; i <= size ; i++) if (f[i][1] & 1)   ans = min(ans , val[i]); 
        printf("%lld\n",ans);
    }
    return 0;
}
C.cpp

 

转载于:https://www.cnblogs.com/ljc20020730/p/11335626.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值