COCI 2015-2016 #5 简要题解

A:ZAMKA

\(\text{Problem}\)题目链接

\(\text{Solution}\)

观察到 \(L,D\) 范围很小,直接暴力从 \(L\) 扫到 \(D\) 找出 \(N\),然后从 \(D\) 扫到 \(L\),找出 \(M\)

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
#define int long long
#define double long double
using namespace std;
inline int read()
{
    int s=0, w=1; ri char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
    return s*w;针对
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
inline bool Check(int x,int n)
{
    int res=0;
    while(x) res+=x%10, x/=10;
    return (res==n);
}
signed main()
{
    int L,D,X; L=read(), D=read(), X=read();
    for(ri int i=L;i<=D;i++)
    {
        if(Check(i,X)) { printf("%lld\n",i); break; }
    }
    for(ri int i=D;i>=L;i--)
    {
        if(Check(i,X)) { printf("%lld\n",i); break; }
    }
    return 0;
}

B:MULTIGRAM

\(\text{Problem}\)题目链接

\(\text{Solution}\)

发现词根长度必然是原串长度 \(n\) 的因数。考虑到 \(1e5\) 内对于每个 \(n\)\(d_{n}\) 都非常小,所以可以直接对于 \(n\) 的因数进行暴力循环。判断两段循环节的字符数是否匹配可以用桶来解决。

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
#define int long long
#define double long double
using namespace std; const int N=200010;
inline int read()
{
    int s=0, w=1; ri char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
    return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int n,ch[2][26]; char s[N];
signed main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for(ri int i=1;i<n;i++)
    {
        if(n%i) continue;
        int qwq=1;
        memset(ch,0,sizeof(ch));
        for(ri int j=0;j<n/i;j++)
        {
            for(ri int k=1;k<=i;k++)
            {
                int x=s[j*i+k];
                ch[j&1ll][x-'a']++;
            }
            if(!j) continue;
            int flg=1;
            for(ri int k=0;k<26;k++)
            {
                if(ch[j&1ll][k]^ch[(j+1)&1ll][k]) { flg=0; break; }
                ch[(j+1)&1ll][k]=0;
            }
            if(!flg) { qwq=0; break; }
        }
        if(qwq)
        {
            for(ri int j=1;j<=i;j++) printf("%c",s[j]);
            puts("");
            return 0;
        }
    }
    puts("-1");
    return 0;
}

C:PERICA

\(\text{Problem}\)题目链接

\(\text{Solution}\)

考虑对原序列排序,对答案没有影响。设选出的 \(K\) 个数最大的是 \(a_{i}\),则我们需要在 \(i-1\) 个数中选 \(K-1\) 个数,答案为:

\[\qquad ans=\sum\limits_{i=1}^{n}a_{i}\times C(i-1,K-1) \qquad \]

直接线性预处理组合数即可。

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
//#define int long long
//#define double long double
using namespace std; const int N=200010, Mod=1e9+7;
inline int read()
{
    int s=0, w=1; ri char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();题目链接
    return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int fac[N+10],inv[N+10];
int n,K,a[N],res;
inline int C(int x,int y) { if(x<y) return 0; return 1ll*fac[x]*inv[x-y]%Mod*inv[y]%Mod; }
inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=1ll*x*x%Mod) if(p&1) res=1ll*res*x%Mod; return res; }
signed main()
{
    n=read(), K=read();
    for(ri int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+1+n);
    fac[0]=1;
    for(ri int i=1;i<=N;i++) fac[i]=1ll*fac[i-1]*i%Mod;
    inv[N]=ksc(fac[N],Mod-2);
    for(ri int i=N;i;i--) inv[i-1]=1ll*inv[i]*i%Mod;
    for(ri int i=K;i<=n;i++) res=(res+1ll*C(i-1,K-1)*a[i]%Mod)%Mod;
    printf("%d\n",res);
    return 0;
}

D:POPLAVA

\(\text{Problem}\)题目链接

\(\text{Solution}\)

构造题。考虑 \(X\) 值的上限。显然,当两端是 \(n-1\)\(n\) 时,\(X=\frac{(n-1)\times(n-2)}{2}\)。所以当 \(X>\frac{(n-1)\times(n-2)}{2}\) 时,无解,否则我们考虑是否一定有解。接下来我们默认考虑 \(n\geq3\) 的情况。

事实上,在两端是 \(n-1\)\(n\) 的情况下,\(1-(n-2)\) 的每个数对答案的贡献是 \(n-i-1\)。考虑我们有一个集合 \(S=\{i\}(1\leq i\leq n-2)\),能否用这个集合的任意子集凑出 \(0-\frac{(n-1)\times(n-2)}{2}\) 内的一个数。显然是可行的。所以我们只要在 \(S\) 中选出一些 \(i\) 使得它们的和为 \(X\),然后在两端放上 \(n-1\)\(n-2\),剩下的数按照某种顺序排序使得它们不会产生贡献即可。

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
#define int long long
#define double long double
using namespace std; const int N=1000010;
inline int read()
{
    int s=0, w=1; ri char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
    return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int n,X,book[N];
signed main()
{
    n=read(), X=read();
    if(X>(n-1)*(n-2)/2) { puts("-1"); return 0; }
    for(ri int i=n-2,t=X;i;i--)
    {
        if(t>=i) book[n-i-1]=1, t-=i;
    }
    printf("%lld ",n-1);
    for(ri int i=1;i<n-1;i++) if(book[i]) printf("%lld ",i);
    printf("%lld ",n);
    for(ri int i=n-2;i;i--) if(!book[i]) printf("%lld ",i);
    puts("");
    return 0;
}

E:OOP

\(\text{Problem}\)题目链接

\(\text{Solution}\)

考虑到模式串中只有一个 * 号,对于一个询问串 \(t_{i}\),可以把它根据 * 的位置分成一个前缀串和后缀串,并分别记它们为 \(a_{i}\)\(b_{i}\)。对于单词串 \(s_{i}\),可以考虑把它插入字典树。发现每次查询时可以把 \(a_{i}\) 放到字典树上查询到节点 \(x\),那么在 \(x\) 的子树内查询有多少和 \(t_{i}\) 相等的后缀即可。

对于后缀串判断是否相同,考虑使用 \(\text{Hash}\)。在字典树上的每个节点开一个 \(vector\) 记录这个节点所有后缀串的 \(\text{Hash}\) 值。对于查询串的 \(b_{i}\) 串,可以用 \(set\)\(map\) 保存它们。那么我们 \(DFS\) 一遍这个字典树,对于一个节点 \(x\),如果这个节点存储的后缀串 \(\text{Hash}\)\(v\),和某个 \(b_{i}\) 串的 \(\text{Hash}\) 值相同,则我们对于 \(v\) 开一个用 \(map\) 实现的 \(vector\) \(G\) ,在 \(G_{v}\) 中插入 \(x\) 这个位置的 \(id\)

考虑如何求出这个 \(id\),由于我们在查询时要求出第一个在子树内和第一个在子树外的 \(id\)\(G_{v}\) 中的位置,则我们可以用欧拉序维护这个信息。记 \(ql_{x}\) 表示第一次遍历 \(x\) 这个节点,\(qr_{x}\) 表示第二次遍历 \(x\) 这个节点,\(DFS\) 字典树时,满足条件就在 \(G_{v}\) 中插入 \(ql_{x}\),那么查询答案时直接在 \(G_{b_{i}}\) 上二分即可找到答案。

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
//#define int long long
//#define double long double
using namespace std; const int N=3000010, M=100010;
const int Base=193, Mod=998244353, Base1=131, Mod1=19260817;
inline int read()
{
    int s=0, w=1; ri char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
    return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int n,q,ch[N][26],tot=1,ql[N],qr[N],nowid; char s[N];
struct Node{ int hsh1,hsh2; }hsh[N];
inline bool operator < (const Node& a, const Node &b ) { return a.hsh1==b.hsh1?a.hsh2<b.hsh2:a.hsh1<b.hsh1; }
vector<Node> P[N];
string a[M],b[M],ss;
set<Node> suf; map<Node,vector<int> > G;
inline void Insert(char *s,int len)
{
    int x=1; P[x].push_back(hsh[0]);
    for(ri int i=0;i<len;i++)
    {
        int p=s[i]-'a';
        if(!ch[x][p]) ch[x][p]=++tot;
        x=ch[x][p];
        P[x].push_back(hsh[i+1]);
    }
}
void DFS(int x)
{
    ql[x]=++nowid;
    for(ri int i=0;i<(int)P[x].size();i++) if(suf.count(P[x][i])) G[P[x][i]].push_back(ql[x]);
    for(int i=0;i<26;i++) if(ch[x][i]) DFS(ch[x][i]);
    qr[x]=++nowid;
}
int Ask(const char *s,int len,Node w)
{
    int x=1;
    for(ri int i=0;i<len;i++)
    {
        int p=s[i]-'a';
        x=ch[x][p];
    }
    return upper_bound(G[w].begin(),G[w].end(),qr[x])-lower_bound(G[w].begin(),G[w].end(),ql[x]);
}
signed main()
{
    n=read(), q=read();
    for(ri int i=1;i<=n;i++)
    {
        scanf("%s",s); int len=strlen(s);
        hsh[len].hsh1=hsh[len].hsh2=0;
        for(ri int j=len-1;~j;j--)
        {
            hsh[j].hsh1=(1ll*hsh[j+1].hsh1*Base%Mod+s[j]-'a'+1)%Mod;
            hsh[j].hsh2=(1ll*hsh[j+1].hsh2*Base1%Mod1+s[j]-'a'+1)%Mod1;
        }
        Insert(s,strlen(s));
    }
    for(ri int i=1;i<=q;i++)
    {
        cin>>ss;
        a[i]=ss.substr(0,ss.find('*'));
        b[i]=ss.substr(ss.find('*')+1);
        int len=b[i].length();
        int sufhash, sufhash1; sufhash=sufhash1=0;
        for(ri int j=len-1;~j;j--)
        {
            sufhash=(1ll*sufhash*Base%Mod+b[i][j]-'a'+1)%Mod;
            sufhash1=(1ll*sufhash1*Base1%Mod1+b[i][j]-'a'+1)%Mod1;
        }
        Node sufh=(Node){sufhash,sufhash1};
        suf.insert(sufh);
    }
    DFS(1);
    for(ri int i=1;i<=q;i++)
    {
        int len=b[i].length();
        int sufhash, sufhash1; sufhash=sufhash1=0;
        for(ri int j=len-1;~j;j--)
        {
            sufhash=(1ll*sufhash*Base%Mod+b[i][j]-'a'+1)%Mod;
            sufhash1=(1ll*sufhash1*Base1%Mod1+b[i][j]-'a'+1)%Mod1;
        }
        Node sufh=(Node){sufhash,sufhash1};
        printf("%d\n",Ask(a[i].c_str(),(int)a[i].size(),sufh));
    }
    return 0;
}

F:PODNIZOVI

\(\text{Problem}\)题目链接

\(\text{Solution}\)

如果 \(a_{i}\) 互不相同,一种暴力的做法是建出子序列自动机,在上面暴力转移。由于每转移一条边就会贡献一次答案,故复杂度正确。

现在有若干个相同子序列的结束位置 \(p_{1},p_{2},...,p_{k}\),考虑对于每个 \(i\),枚举所有权值为 \(i\) 的位置 \(pos\),将小于 \(pos\)\(p_{j}\) 计入答案即可。

不难发现上面做法的瓶颈在于枚举 \(i\)。考虑倍增优化,如果权值为 \(i+2^{j}\) 的最大位置比 \(p_{1}\) 小,则对答案不会产生贡献。

考虑时间复杂度的正确性:每次倍增找到的 \(i\) 必然会对答案产生贡献,且每次枚举位置都会使得 \(K\)\(1\)。而单次找 \(i\) 的复杂度为 \(O(\log n)\),故总时间复杂度为 \(O(n\log n)\),可以通过。

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=100010, M=18;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int n,K,B,Mod,a[N],mx,st[N][M];
vector<int> g[N];
void Solve(vector<int> now,int hsh)
{
	int plc=now[0];
	for(ri int i=1;i<=mx;i++)
	{
		for(ri int j=M-1;~j&&i<=mx;j--) if(st[i][j] && st[i][j]<=plc) i+=(1<<j);
		if(i>mx) break;
		vector<int> nxt;
		int fir=upper_bound(g[i].begin(),g[i].end(),plc)-g[i].begin();
		int ot=(1ll*hsh*B%Mod+i)%Mod;
		for(ri int j=fir;j<(int)g[i].size();j++)
		{
			for(auto k:now)
			{
				if(k>=g[i][j]) break;
				printf("%d\n",ot);
				K--;
				if(!K) exit(0);
				nxt.eb(g[i][j]);
			}
		}
		if(nxt.size()) Solve(nxt,ot);
	}
}
signed main()
{
	n=read(), K=read(), B=read(), Mod=read();
	for(ri int i=1;i<=n;i++) a[i]=read(), st[a[i]][0]=i, g[a[i]].eb(i), mx=max(mx,a[i]);
	for(ri int i=1;(1<<i)<=mx;i++)
	for(ri int j=1;j+(1<<i)-1<=mx;j++)
	st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
	vector<int> p; p.eb(-1);
	Solve(p,0);
	return 0;
}
posted @ 2020-08-06 10:57  zkdxl  阅读(287)  评论(1编辑  收藏  举报