给出一个由数字(‘0’-‘9’)构成的字符串。我们说一个子序列是好的,如果他的每一位都是 1、8、0、7 ,并且这四个数字按照这种顺序出现,且每个数字都出现至少一次(111888888880000007 是好的,而 1087 不是)。请求出最大的好的子序列的长度。
T1:1807
题目描述
输入格式
输入唯一一行一个字符串。
输出格式
一行一个整数表示答案。
考虑DP,用f[i][j]表示到了第i位,最后一位是j(j=1,8,0,7)的子序列的最长长度(不一定是完整的子序列),当j的必要前导数结尾的最长子序列不为零(即f[i][g(j)]!=0),转移更新f[i][j]=max(f[i-1][j]+1,f[i-1][g(j)]+1),1比较特殊,直接用f[i][0]=f[i-1][0]+1转移,注意当数字不为1,8,0,7时,状态也要进行转移。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
int f[1000010][5];
char s[1000010];
int main()
{
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;++i)
{
if(s[i]=='1')
{
f[i][0]=f[i-1][0]+1;
f[i][1]=f[i-1][1];
f[i][2]=f[i-1][2];
f[i][3]=f[i-1][3];
}
else
if(s[i]=='8')
{
if(f[i-1][0])
f[i][1]=max(f[i-1][1]+1,f[i-1][0]+1);
else f[i][1]=f[i-1][1];
f[i][0]=f[i-1][0];
f[i][2]=f[i-1][2];
f[i][3]=f[i-1][3];
}
else
if(s[i]=='0')
{
if(f[i-1][1])
f[i][2]=max(f[i-1][2]+1,f[i-1][1]+1);
else f[i][2]=f[i-1][2];
f[i][1]=f[i-1][1];
f[i][0]=f[i-1][0];
f[i][3]=f[i-1][3];
}
else
if(s[i]=='7')
{
if(f[i-1][2])
f[i][3]=max(f[i-1][2]+1,f[i-1][3]+1);
else f[i][3]=f[i-1][3];
f[i][1]=f[i-1][1];
f[i][2]=f[i-1][2];
f[i][0]=f[i-1][0];
}
else
{
f[i][1]=f[i-1][1];
f[i][2]=f[i-1][2];
f[i][0]=f[i-1][0];
f[i][3]=f[i-1][3];
}
}
cout<<f[len][3]<<endl;
return 0;
}
T2: minimum
题目描述
给出一幅由 n 个点 m 条边构成的无向带权图。
其中有些点是黑点,另外点是白点。
现在每个白点都要与他距离最近的所有黑点通过最短路连接(与所有最近的黑点连接,但必须用最短路上的边),我们想要使得花费的代价最小。请问这个最小代价是多少?
注意:最后选出的边保证每个白点到黑点的距离任然是最短距离。
输入格式
第一行两个整数 n,m ;
第二行 n 个整数,0 表示白点,1 表示黑点;
接下来 m 行,每行三个整数 x,y,z ,表示一条连接 x 和 y 点,权值为 z 的边。
输出格式
如果无解,输出“impossible”,否则,输出最小代价。
建一个超级点与所有的黑点以权值为0的边相连,从超级点开始跑最短路,再做一个类似最小生成树的东西,一条边除了权值小之外还要满足dis[edge[i].to]==dis[x]+edge[i].w,即这条边是最短路上的边,最后得出ans。(这不一定要使所有点联通)
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
const int N=200010;
int n,m;
struct node{
int to,next,u;
long long w;
}edge[N*4];
int first[N],stack[N*4],cnt,p[N],fa[N];
long long dis[N];
bool vis[N],hei[N];
int read()
{
int x=0;
char ch;
int f=1;
for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;ch>='0'&&ch<='9';ch=getchar())
x=(x<<3)+(x<<1)+ch-'0';
return x*f;
}
void add(int x,int y,int z)
{
edge[++cnt].to=y;
edge[cnt].next=first[x];
first[x]=cnt;
edge[cnt].w=z;
edge[cnt].u=x;
}
void spfa()
{
memset(dis,127,sizeof(dis));
int head=0;
int tail=1;
dis[0]=0;
vis[0]=true;
stack[1]=0;
while(head<tail)
{
head++;
int x=stack[head];
vis[x]=false;
for(int i=first[x];i;i=edge[i].next)
{
if(dis[edge[i].to]>dis[x]+edge[i].w)
{
dis[edge[i].to]=dis[x]+edge[i].w;
if(!vis[edge[i].to])
{
tail++;
vis[edge[i].to]=true;
stack[tail]=edge[i].to;
}
}
}
}
}
bool comp(node x,node y)
{
return x.w<y.w;
}
int search(int x)
{
if(x==fa[x]) return x;
fa[x]=search(fa[x]);
return fa[x];
}
int main()
{
n=read();
m=read();
int top=0;
for(int i=1;i<=n;++i)
{
int x=read();
if(x==1)
{
p[++top]=i;
hei[i]=true;
}
}
for(int i=1;i<=m;++i)
{
int x,y,z;
x=read();
y=read();
z=read();
add(x,y,z);
add(y,x,z);
if(hei[x]==true||hei[y]==true)
{
hei[x]=true;
hei[y]=true;
}
}
for(int i=1;i<=n;++i)
if(hei[i]==false) {cout<<"impossible";return 0;}
for(int i=1;i<=top;++i)
add(0,p[i],0),add(p[i],0,0);
spfa();
sort(edge+1,edge+1+cnt,comp);
for(int i=1;i<=n;++i) fa[i]=i;
long long ans=0;
for(int i=1;i<=cnt;++i)
{
if(dis[edge[i].to]==dis[edge[i].u]+edge[i].w)
{
int x=edge[i].u,y=edge[i].to;
if(search(x)!=search(y))
{
int fx=search(x);
int fy=search(y);
fa[fx]=fy;
ans+=edge[i].w;
}
}
}
cout<<ans;
return 0;
}
T3:gcd
题目描述
给出n个正整数,放入数组 a 里。
问有多少组方案,使得我从 n 个数里取出一个子集,这个子集的 gcd 不为 1 ,然后我再从剩下的数中取出一个数,把他放进刚刚取出的子集里,使得 gcd 为 1 。
输出方案数 mod (109 + 7)。
输入格式
第一行一个数 n 。
第二行 n 个数,表示 a 数组。
输出格式
输出一个数表示答案。
考虑容斥原理,利用莫比乌斯函数(orz..)。。。蒟蒻在这里就不深究了,不过30%的数据可以暴力枚举每一个子集再check加的数。就这样吧。
总结:
这次考试1,2题都是很好的训练题,T1是一道难度不高但思想很好的DP题,T2作为一道图论题,将最短路与最小生成树结合在一起(这两个算法经常一起思考,以(dis[edge[i].to]==dis[x]+edge[i].to)作为桥梁),同时超级点的应用也很值得多思考。T3作为NOIP的模拟测试有一点超了,但是30%的数据暴力分要好好拿到手。