【胡策08】解题报告

【第一题】

题意:

给一个 01 串设为其 S,询问是否存在只出现两次的 01 串 T。

这里的出现定义为存在一串下标 a_1,a_2,...,a_m,满足 a_1<a_2<...<a_m 且 S_{a_i}=T_i

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;
}
DFS

O(n)的写法应该是找0110或1001?暂时理解不了……(> <)

【第二题】

题意:

给长度为 n 的数列 A 和长度为 m 的数列 B,问有多少长度为 m 的数列 C 满足

1 leq C_1<C_2<...<C_mleq n

(A_{c_1}+B_1) leq (A_{c_2}+B_2) leq ... leq (A_{c_m}+B_m)

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;
}
DP+树状数组

【第三题】

题意:给一个图,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; 
}
dijkstra

正解:

考虑更简单的情况,若每条边没有边权,显然用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=

原文地址:https://www.cnblogs.com/onioncyc/p/7246315.html