FOI2019算法冬令营D1
A. 全连
1s,256MB
E.Space 喜欢打音游。
但是他技术不好,总是拿不到全连(Full Combo)。
现在他面前有一份乐谱,乐谱的其中一段有 nnn个连续的单键音符。
相邻两个音符的到来时间均相等,我们可以认为第 iii 个音符会在第 iii 个时刻到来。
点击一个音符,E.Space 需要一段准备时间来进行移动手指之类的操作。由于音符的位置和周围情况不同,点击每个音符的准备时间也不同。
在一个音符的准备时间内,E.Space 没法做到去点击其它音符,但是不同音符的准备时间范围可以互相重叠。形式化地,令第 iii 个音符的准备时间为 tit_iti 个单位时间,那么如果 E.Space 选择去点击第 iii 个音符,那么他就没法点击所有到来时刻在 (i−ti,i+ti)(i − t_i ,i + t_i)(i−ti,i+ti) 中的音符。
为了获得更高的分数,E.Space 还计算了每个音符的性价比aia_iai。一个音符的性价比等于点击这个音符得到的分数除以 E.Space 点击它所需要的准备时间。
E.Space 就不指望全连了,他只是想让你帮他计算一下他最多可以得到多少分数。
n<=1e6,ai<=1e9n<=1e6,a_i<=1e9n<=1e6,ai<=1e9
题解
大大的良心题啊
很好想到dp:fif_ifi表示前iii个数,iii为最后一个取的最高得分
显然fi=fj+ai∗tif_i=f_j+a_i*t_ifi=fj+ai∗ti
其中j+tj<=i,i−ti>=jj+t_j<=i,i-t_i>=jj+tj<=i,i−ti>=j
所以我们可以写个堆,里面的元素以x+txx+t_xx+tx排序
当计算fif_ifi的时候,就可以把堆里面小于iii的元素弹出,将其dp值插入到线段树中,然后就是区间查找最大值
上代码
#include <cstdio>
#include <queue>
#include <algorithm>
#define Ls k<<1
#define Rs k<<1|1
#define mid ((l+r)>>1)
#define LL long long
using namespace std;
const int N=1e6+5;
int n,t[N];LL f[N],ax[N*4],ans;
struct O{
int x;
friend bool operator < (const O& A,const O& B){
return A.x+t[A.x]>B.x+t[B.x];
}
};
priority_queue<O>q;
void update(int k,int l,int r,int x,LL v){
if (l==r){ax[k]=v;return;}
if (mid>=x) update(Ls,l,mid,x,v);
else update(Rs,mid+1,r,x,v);
ax[k]=max(ax[Ls],ax[Rs]);
}
LL query(int k,int l,int r,int L,int R){
if (L>R) return 0;
if (L<=l && r<=R) return ax[k];
if (mid<L) return query(Rs,mid+1,r,L,R);
if (mid>=R) return query(Ls,l,mid,L,R);
return max(query(Ls,l,mid,L,R),query(Rs,mid+1,r,L,R));
}
int main(){
freopen("fc.in","r",stdin);
freopen("fc.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&t[i]);
for (int i=1;i<=n;i++){
LL x;scanf("%lld",&x);f[i]=x*t[i];
while(!q.empty()){
O k=q.top();if (k.x+t[k.x]>i) break;
q.pop();update(1,1,n,k.x,f[k.x]);
}
f[i]+=query(1,1,n,1,i-t[i]);
ans=max(ans,f[i]);q.push((O){i});
}
printf("%lld\n",ans);
return 0;
}
B. 原样输出
3s,256MB
nealchen 是一只 copycat。
它会把输入按行读入,原封不动地复制到输出中去。
但是在一次更新以后,它的程序出了一些问题。
它没法输出换行符了。
并且,读入的时候,总会莫名其妙地随机漏掉开头和结尾的若干个字符,甚至整行都会漏掉。
比如 orznight 可能会变成 rzni ,orz,h 或者空串。
现在你找到一份输入文件丢给 nealchen,你想知道它的输出可能有多少种情况,以及每种情况分别是什么。
由于你找到的输入文件全部来自之前的福建省选,所以所有的输入文件每行只可能包含 A,C,G,T 四种字符。
保证输入文件大小不超过 1MB ,保证输出文件大小不超过 200MB 。
题解
如果只有一个串,就是后缀自动机的基本应用
假设我们在匹配一个字符串能否成为答案,那我们就是贪心的匹配
所以我们从后往前建n个后缀自动机,然后在建立第i个SAM的时候看每个点是不是都有ACGT的出边,如果没有的话,那就往i+1的rt的对应出边节点连边
然后得到一个拓扑图,可以dp出答案,输出方案就直接dfs
上代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=3e6+5,P=1e9+7;
int d[N],ans,sz,lst,k,n,m[N],las[4],rt[N],in[N];
string s[N];queue<int>q;vector<int>gg[N];char g[N];
struct SAM{int len,link,mp[4];}sam[N];
void sam_con(int d,int ch){
int np=++sz,p=lst;
sam[np].len=sam[p].len+1;
while(p && !sam[p].mp[ch])
sam[p].mp[ch]=np,p=sam[p].link;
if (!p) sam[np].link=rt[d];
else{
int q=sam[p].mp[ch];
if (sam[q].len==sam[p].len+1)
sam[np].link=q;
else{
int nq=++sz;
sam[nq].len=sam[p].len+1;
sam[nq].link=sam[q].link;
for (int i=0;i<4;i++)
sam[nq].mp[i]=sam[q].mp[i];
sam[q].link=sam[np].link=nq;
while(p && sam[p].mp[ch]==q)
sam[p].mp[ch]=nq,p=sam[p].link;
}
}
lst=np;
}
void dfs(int id,int x,int j){
if (j==0) g[x]='A';if (j==1) g[x]='C';
if (j==2) g[x]='G';if (j==3) g[x]='T';
for (int i=0;i<=x;i++)
putchar(g[i]);putchar('\n');
for (int i=0;i<4;i++)
if (sam[id].mp[i])
dfs(sam[id].mp[i],x+1,i);
}
int main(){
freopen("copy.in","r",stdin);
freopen("copy.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
cin>>s[i],m[i]=s[i].length();
for (int i=n;i;i--){
lst=++sz;rt[i]=sz;
for (int y,j=0;j<m[i];j++){
if (s[i][j]=='A') y=0;
if (s[i][j]=='C') y=1;
if (s[i][j]=='G') y=2;
if (s[i][j]=='T') y=3;
sam_con(i,y);
}
for (int j=rt[i];j<=sz;j++)
for (int l=0;l<4;l++){
if (!sam[j].mp[l])
sam[j].mp[l]=las[l];
if (sam[j].mp[l])
gg[sam[j].mp[l]].push_back(j),
in[j]++;
}
for (int l=0;l<4;l++)
if (sam[rt[i]].mp[l])
las[l]=sam[rt[i]].mp[l];
}
scanf("%d",&k);
if (k){
puts("");ans++;
for (int i=0;i<4;i++)
if (sam[rt[1]].mp[i])
dfs(sam[rt[1]].mp[i],0,i);
}
for (int i=1;i<=sz;i++){
d[i]=1;
if (!in[i]) q.push(i);
}
while(!q.empty()){
int x=q.front();q.pop();
for (int j,i=gg[x].size()-1;~i;i--){
j=gg[x][i];(d[j]+=d[x])%=P;in[j]--;
if (!in[j]) q.push(j);
}
}
printf("%d\n",d[rt[1]]);
return 0;
}
C. 不同的缩写
1s,256MB
你在写一款 Galgame 的剧情(的代码)。
在这个游戏中一共有 n 个角色。你需要编写一些关于这些角色的对话内容。然而,在写这些对话内容之前,都要写一段关于角色信息的代码,就像这样:
Character(“Alex”, color = “#FFFC3A”)
你觉得这样好麻烦。你决定把它简化一下。你打算用角色名字的一个非空子序列(可以不连续)来作为它的简称。
当然,不同的角色要用不同的字符串作为简称,否则你就变量重名了。
你想确定一个简称的分配方案使得所有角色中最长的简称尽量短,这样你打起代码就会方便一些。
n,len<=300
题解
首先二分答案mid,然后对于每个串,找出长度<=mid的子序列
注意:当找出n个子序列的时候就可以不用找了,因为这n个中肯定有一个可以作为答案
那我们就对找到的子序列建点,和字符串下标i连边,然后跑二分图匹配,如果满流则为合法
复杂度玄学,上代码
#include <bits/stdc++.h>
#define LL unsigned long long
#define K 793999
using namespace std;bool G;
const int N=333,M=444444;
int n,Ax,l=1,r,g[N],cp,nex[M],d[M];
int S,T,head[M],cur[M],V[M],W[M],t,dd[M];
char s[N][N];LL h[M],gg;string H[M],ggg,ys[M];
map<LL,int>p,q;queue<int>Q;
inline void ins(int u,int v){
V[++t]=v;W[t]=1;nex[t]=head[u];head[u]=t;
V[++t]=u;W[t]=0;nex[t]=head[v];head[v]=t;
}
inline bool bfs(){
for (int i=S;i<=T;i++) d[i]=-1;
Q.push(S);d[S]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
for (int i=head[x];i;i=nex[i])
if (d[V[i]]==-1 && W[i])
d[V[i]]=d[x]+1,Q.push(V[i]);
}
return d[T]>-1;
}
int dfs(int x,int ax){
if (x==T) return ax;
int sum=0,tt;
for (int& i=cur[x];i;i=nex[i]){
if (W[i] && d[V[i]]==d[x]+1
&& (tt=dfs(V[i],min(ax,W[i]))))
ax-=tt,W[i]-=tt,W[i^1]+=tt,sum+=tt;
if (!ax) break;
}
return sum;
}
inline void work(int d,int x){
for (int j=1,i=1;i<=g[d];i++)
for (int k=j;k;k--){
if (dd[k]==x) continue;
gg=h[k]*K+s[d][i];
if (G) ggg=H[k]+s[d][i];
if (q[gg]!=d){
h[++j]=gg,q[gg]=d;
dd[j]=dd[k]+1;
if (G) H[j]=ggg;
if (!p[gg]){
p[gg]=++cp;
if (G) ys[cp]=ggg;
}
ins(d,p[gg]+n);
}
if (j==n+1) return;
}
}
inline bool J(int x){
t=1;p.clear();cp=0;q.clear();
for (int i=S;i<=T;i++) head[i]=0;
for (int i=1;i<=n;i++)
ins(S,i),work(i,x);
T=cp+n+1;
for (int i=1;i<=cp;i++) ins(i+n,T);
int sum=0;while(bfs()){
for (int i=S;i<=T;i++)
cur[i]=head[i];
sum+=dfs(S,1e9);
}
if (G)
for (int i=1;i<=n;i++)
for (int j=head[i];j;j=nex[j])
if (V[j] && !W[j]){
int ll=ys[V[j]-n].length();
for (int l=0;l<ll;l++)
putchar(ys[V[j]-n][l]);
putchar('\n');break;
}
return sum==n;
}
int main(){
freopen("diff.in","r",stdin);
freopen("diff.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%s",s[i]+1),
g[i]=strlen(s[i]+1),
Ax=max(Ax,g[i]);
r=Ax+1;while(l<r){
int mid=(l+r)>>1;
if (J(mid)) r=mid;
else l=mid+1;
}
if (l>Ax) puts("-1");
else{
printf("%d\n",l);G=1;
return !J(l);
}
return 0;
}