【第一题】
题意:
给一个 01 串设为其 S,询问是否存在只出现两次的 01 串 T。
这里的出现定义为存在一串下标 ,满足 且 。
2≤n≤5000,数据随机。
题解:
很容易想到部分分算法DFS枚举子集。
由于数据随机,n>10时大概率存在,直接输出。
#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<algorithm> #define ll long long using namespace std; int read() { char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f; int n,a[20][3000];bool ok[20],b[20]; char s[20]; void dfs(int x) { if(x==n+1) { int ans=0,tot=0; for(int i=1;i<=n;i++)if(ok[i]) { tot++; ans=ans*2+b[i]; } a[tot][ans]++; } else { ok[x]=0; dfs(x+1); ok[x]=1; dfs(x+1); } } int main() { scanf("%s",s+1); n=strlen(s+1); if(n>10){printf("Y");return 0;} for(int i=1;i<=n;i++)if(s[i]=='1')b[i]=1;else b[i]=0; memset(a,0,sizeof(a)); dfs(1); bool ans=0; for(int i=1;i<=n;i++) for(int j=0;j<=1100;j++) if(a[i][j]==2)ans=1; if(ans)printf("Y");else printf("N"); return 0; }
O(n)的写法应该是找0110或1001?暂时理解不了……(> <)
【第二题】
题意:
给长度为 n 的数列 A 和长度为 m 的数列 B,问有多少长度为 m 的数列 C 满足
n≤2000,m≤1000
题解:
很容易想到部分分算法DP。
f[i][j]=Σf[k][j-1],k<i&&满足条件
复杂度O(n*m*n),考虑优化。
改变枚举顺序,将j作为第一维枚举,用树状数组维护。
令c为a重排序后数组,由于条件为c[k]+b[j-1]<=c[i]+b[j],其中排序后k递增,就可以从小到大维护每个值对应的转移来源上限g[i],方便待会查询时映射过来。
上面过程限制数值大小,然后用树状数组1~n查一个插一个限制坐标大小。
复杂度O(m*n*log(n))。
#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<algorithm> #define ll long long using namespace std; int read() { char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f,maxn=2010,MOD=1000000007; int n,m,tot,t[maxn],a[maxn],b[maxn],c[maxn],d[maxn],f[maxn],g[maxn]; int mods(int x){return x>=MOD?x-MOD:x;} struct node{int x,id;}cyc[maxn]; int lowbit(int x){ return x&(-x);} void modify(int x,int y){ while(x<=tot) t[x]=mods(t[x]+y),x+=lowbit(x);} int query(int x){ int s=0; while(x) s=mods(s+t[x]),x-=lowbit(x); return s;} bool cmp(node a,node b){return a.x<b.x;} int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){scanf("%d",&a[i]);cyc[i].x=a[i];cyc[i].id=i;} for(int i=1;i<=m;i++)scanf("%d",&b[i]); sort(cyc+1,cyc+n+1,cmp); for(int i=1;i<=n;i++)if(cyc[i].x==cyc[i-1].x){d[cyc[i].id]=tot;}else{c[++tot]=cyc[i].x;d[cyc[i].id]=tot;} for(int i=1;i<=n;i++)f[i]=1; for(int j=2;j<=m;j++) { g[0]=0; for(int i=1;i<=tot;i++) { g[i]=g[i-1]; while(g[i]+1<=tot&&c[g[i]+1]+b[j-1]<=c[i]+b[j])g[i]++; } memset(t,0,sizeof(t)); modify(d[j-1],f[j-1]); for(int i=j;i<=n;i++) { int tmp=f[i]; f[i]=query(g[d[i]]); modify(d[i],tmp); } } int ans=0; for(int i=m;i<=n;i++)ans=mods(ans+f[i]); printf("%d",ans); return 0; }
【第三题】
题意:给一个图,n 个点 m 条双向边,每条边有其长度。n 个点中有 k 个是特殊点,问任意两个特殊点的最短路是多少。
n≤10^5,m≤3*10^5,k≤10^4。
题解:
考试时想到其实一遍dijkstra理论上已经可以得到全图信息,不应该需要k次。
结合dijkstra可以设置多源最短路(起点集),想到了设置多起点然后记录每个点的最短路和次短路(维护它们来自不同的特殊点),一边统计答案。
复杂度O(m log n)
#include<cstdio> #include<algorithm> #include<cstring> #include<cctype> #include<queue> using namespace std; const int maxn=100010,maxm=600010,inf=0x3f3f3f3f; int read() { char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } struct edge{int from,v,w;}e[maxm]; struct Node{int x,d,id;}cyc; int n,m,first[maxn],tot,d[maxn],t,k,s[10010],ans,d2[maxn],g[maxn],g2[maxn]; priority_queue<Node>q; bool operator <(Node a,Node b) {return a.d>b.d;} void insert(int u,int v,int w) {tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} void dijkstra() { for(int i=1;i<=n;i++){d[i]=inf;d2[i]=inf;g[i]=g2[i]=0;} for(int i=1;i<=k;i++) { d[s[i]]=0;g[s[i]]=s[i]; cyc.x=s[i],cyc.d=0;cyc.id=s[i];q.push(cyc); } while(!q.empty()) { cyc=q.top();q.pop(); if(cyc.d!=d[cyc.x])continue; int x=cyc.x; for(int i=first[x];i;i=e[i].from) if(d[e[i].v]!=inf) { if(g[e[i].v]!=cyc.id)ans=min(ans,d[e[i].v]+e[i].w+d[x]);else ans=min(ans,d2[e[i].v]+e[i].w+d[x]); if(d[e[i].v]>d[x]+e[i].w&&g[e[i].v]!=cyc.id) { d2[e[i].v]=d[e[i].v]; g2[e[i].v]=g[e[i].v]; d[e[i].v]=d[x]+e[i].w; g[e[i].v]=cyc.id; cyc.x=e[i].v,cyc.d=d[e[i].v]; q.push(cyc); } else if(d2[e[i].v]>d[x]+e[i].w&&g[e[i].v]!=cyc.id) { d2[e[i].v]=d[x]+e[i].w; g2[e[i].v]=cyc.id; } } else { d[e[i].v]=d[x]+e[i].w;g[e[i].v]=cyc.id; cyc.x=e[i].v,cyc.d=d[e[i].v]; q.push(cyc); } } } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=k;i++)s[i]=read(); for(int i=1;i<=m;i++) { int u=read(),v=read(),w=read(); insert(u,v,w); insert(v,u,w); } ans=inf; dijkstra(); printf("%d",ans); return 0; }
正解:
考虑更简单的情况,若每条边没有边权,显然用BFS,那么第一个访问了两次的点路程相加就是答案。
出现边权后,不能使用BFS的原因在于边权不一,有大小边之分。
我们考虑使用优先队列维护,每次处理距离值最小的点,如此便达到了BFS后面访问的点距离值大于前面访问的点的目的。
于是成功实现了带边权图的BFS。
但是由于边权大小不一,不能认定第一个两次访问的结点为答案了,所以把全图bfs完后确定答案。
此时,对于一个点,访问到它的一定是最短和次短,再保证来自两个不同的特殊点,就可以实现访问结点两次就统计答案后不再访问。
复杂度O(m log n)
回来观光一波,发现这个解法,描述的就是dijkstra的原理。
到达每个点的最长路径+来自不同点的次长路径贡献答案,一定能统计到。
由于dijkstra特有的从小到大路径长度保证,所以这样是正确的。
#include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int maxn=100010,maxm=600010,inf=0x3f3f3f3f; struct edge{int v,from,w;}e[maxm]; struct cyc{ int x,y,d; bool operator < (const cyc &x) const {return d>x.d;} }; priority_queue<cyc>q; int n,first[maxn],m,k,s[maxn],d[maxn],b[maxn],tot; bool vis[maxn]; void insert(int u,int v,int w) {tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=k;i++)scanf("%d",&s[i]); for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); insert(u,v,w); insert(v,u,w); } memset(d,0x3f,sizeof(d)); for(int i=1;i<=k;i++){q.push((cyc){s[i],s[i],0});/*d[i]=0;*/} memset(vis,0,sizeof(vis)); int ans=inf; while(!q.empty()) { cyc x=q.top();q.pop(); if(d[x.x]!=inf&&b[x.x]!=x.y){ans=min(ans,d[x.x]+x.d);vis[x.x]=1;} else { d[x.x]=x.d;b[x.x]=x.y; for(int i=first[x.x];i;i=e[i].from) if(b[e[i].v]!=x.y&&!vis[e[i].v])q.push((cyc){e[i].v,x.y,x.d+e[i].w}); } } printf("%d",ans); return 0; }
PS:dijkstra常数比正解小=w=