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\) 个数,答案为:
直接线性预处理组合数即可。
\(\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;
}