16 HK(重温经典)

导语

题目很难,太刺激了属于是

涉及的知识点

图,字符串,思维

链接:16 HK

题目

A

题目大意:定义k色图为一张有n个节点的无向图,每个节点是k种颜色中的一种,不要求相邻相异,现在给出两个k色图s和t,要将s变化到t,变化的规则如下:每一步,每一个点可以选择变化成一个相邻节点的颜色或者保持现在的颜色,给出初始的s和t,判断s是否能到达t

思路:把这个题目想复杂了,一共有100个点,全交换也就1e4的数据量,也就是1e4步,但是题设条件给了20000步,又因为是连通图,所以如果初始图各颜色数量与终点图各颜色数量相同,那么最后初始图一定可以到达终点图,只需要暴力构造模拟即可

代码

#include<bits/stdc++.h>
using namespace std;
const int maxl=110;
int n,m,k,flag,ans,ansv;
int a[maxl],ta[maxl],num[maxl],tnum[maxl];
int frm[maxl];
bool vis[maxl];
vector<int> e[maxl],ee[maxl],b;
inline void predfs(int u) {
    vis[u]=true;
    for(int v:ee[u])
        if(!vis[v]) {
            e[u].push_back(v);//存相邻点
            e[v].push_back(u);
            predfs(v);
        }
    b.push_back(u);//dfs序
}
inline void prework() {
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1; i<=n; i++)//记录一开始图中颜色数
        scanf("%d",&a[i]),num[a[i]]++;
    for(int i=1; i<=n; i++)
        scanf("%d",&ta[i]),tnum[ta[i]]++;//记录终了图中颜色数
    int u,v;
    for(int i=1; i<=m; i++) {//存图
        scanf("%d%d",&u,&v);
        ee[u].push_back(v);
        ee[v].push_back(u);
    }
    predfs(1);
}
inline void print() {//输出过程
    for(int i=1; i<=n; i++)
        printf("%d%c",a[i]," \n"[i==n]);
}
inline void dfs(int u) {
    vis[u]=true;//标记访问
    if(num[a[u]]>tnum[a[u]]) {//找到一个颜色起始比终了多的点
        ansv=u;//记录这个点
        return;
    }
    for(int v:e[u])
        if(!vis[v]) {
            frm[v]=u;
            dfs(v);
            if(ansv) return;
        }
}
inline void findcol(int u,int col) {
    vis[u]=true;
    if(a[u]!=ta[u] && a[u]==col) {//找到一个颜色为col并且当前颜色不与终点相同的点
        ansv=u;//记录下标
        return;
    }
    for(int v:e[u])
        if(!vis[v]) {
            frm[v]=u;
            findcol(v,col);
            if(ansv) return;
        }
}
inline void mainwork() {
    ans=1;
    for(int i=0; i<k; i++)
        if(tnum[i]>0 && num[i]==0)
            ans=0;
    if(!ans) {//变不出来
        puts("Impossible");
        return;
    }
    print();//输出初始图
    int u,v;
    for(int i=0; i<k; i++)
        if(num[i]<tnum[i]) {//找到一种需要增加的颜色i
            for(int j=1; j<=n; j++)
                if(a[j]==i) {//找到一个是该颜色的点j
                    u=j;
                    break;
                }
            while(num[i]<tnum[i]) {
                for(int j=1; j<=n; j++)//清空父标记与访问标记
                    frm[j]=0,vis[j]=false;
                ansv=0;
                dfs(u);
                v=ansv;//记录一个多余的点
                while(frm[v]!=u) {//把这个多余的点换到j的邻接点
                    swap(a[v],a[frm[v]]);
                    v=frm[v];
                    print();
                }
                num[a[v]]--;
                num[i]++;
                a[v]=i;//把这个点涂成需要增加的颜色
                print();
            }
        }
    for(int d:b)
        if(a[d]!=ta[d]) {//如果起始点颜色与终点不同
            for(int i=1; i<=n; i++)//初始化
                frm[i]=0,vis[i]=false;
            ansv=0;
            findcol(d,ta[d]);//找到一个与终点颜色相同的可达点
            v=ansv;
            while(v!=d) {//把找到的点换过去
                swap(a[v],a[frm[v]]);
                v=frm[v];
                print();
            }
        }
}
int main() {
    prework();
    mainwork();
    return 0;
}

C

题目大意:有N个数字,每个数字组成都是 2 a 3 b 2^a3^b 2a3b的形式,现在要对这些数字进行N-1个操作,每个操作选择两个数 X , Y X,Y X,Y,将这两个数合并成 o p ( X , Y ) op(X,Y) op(X,Y),多次操作之后,只剩下一个数字每次操作是 g c d gcd gcd l c m lcm lcm,现在有N个阶段,每个阶段可以操作 g c d gcd gcdk次然后操作 l c m lcm lcmN-1-k次,对每个阶段,求出剩下的最大值与最小值

思路:第一个和最后一个阶段的结果都是定值,对于一般情况,如果想要值最大,需要至少把a和b最多的两个数进行 l c m lcm lcm,其余的数随意,如果想要最小,需要把a和b最小的两个数进行 g c d gcd gcd然后将结果与其他数随意后的结果gcd即可,注意特殊情况的判断

代码

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 5e4+10;
struct num {
    int a,b;
} da[N];
int maxa1[N],mina1[N],maxb1[N],minb1[N];
bool com(num a,num b) {
    return (a.a*log(2)+a.b*log(3)<b.a*log(2)+b.b*log(3));
}
void solve() {
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
        scanf("%d%d",&(da[i].a),&(da[i].b));
    sort(da+1,da+1+n,com);
    maxa1[1]=mina1[1]=da[1].a;
    maxb1[1]=minb1[1]=da[1].b;
    for(int i = 2; i <= n; i++) {
        maxa1[i] = max(maxa1[i-1],da[i].a);
        mina1[i] = min(mina1[i-1],da[i].a);
        maxb1[i] = max(maxb1[i-1],da[i].b);
        minb1[i] = min(minb1[i-1],da[i].b);
    }
    if(n==1)
        printf("%d %d %d %d\n",maxa1[n],maxb1[n],maxa1[n],maxb1[n]);
    if(n==2) {
        printf("%d %d %d %d\n",maxa1[n],maxb1[n],maxa1[n],maxb1[n]);
        printf("%d %d %d %d\n",mina1[n],minb1[n],mina1[n],minb1[n]);
    }
    if(n==3) {
        printf("%d %d %d %d\n",maxa1[n],maxb1[n],maxa1[n],maxb1[n]);
        printf("%d %d %d %d\n",da[n].a,da[n].b,da[1].a,da[1].b);
        printf("%d %d %d %d\n",mina1[n],minb1[n],mina1[n],minb1[n]);
    }
    if(n>=4) {
        printf("%d %d %d %d\n",maxa1[n],maxb1[n],maxa1[n],maxb1[n]);
        printf("%d %d %d %d\n",maxa1[n],maxb1[n],da[1].a,da[1].b);
        for(int i = 3; i < n-1; i++)
            printf("%d %d %d %d\n",maxa1[n],maxb1[n],mina1[n],minb1[n]);
        printf("%d %d %d %d\n",da[n].a,da[n].b,mina1[n],minb1[n]);
        printf("%d %d %d %d\n",mina1[n],minb1[n],mina1[n],minb1[n]);
    }
}
int main() {
    int t = 1;
    while(t--)
        solve();
    return 0;
}

J

题目大意:给出n个01串,求出一个最长的01串s使得这n个01串都不为s的子串

思路:直接构造AC自动机,用DFS查找构造满足条件的字符串并且标记已经访问过的节点,如果在搜索过程中访问到已标记,代表可以组成无限个,有个与基本AC自动机不同的地方:对于一个节点,当需要向下遍历时,需要判断当前节点+一个子节点是否会匹配了输入串,需要从子的fail出发一直向下,如果碰到一个节点是串的结尾说明会匹配输入串,则该子节点不可选,那么反向推理,如果一个节点x是一个串的结尾且该节点x又是另一个节点y的失配位置,那么显然y节点到x的这条道也应当不可选了

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+10;
int trie[maxn][2],End[maxn],fail[maxn],n,cnt,length;
bool vis[maxn];
char s[maxn],res[2][maxn];
void Insert() {
    int p=0,len=strlen(s);
    for(int i=0; i<len; i++) {
        int ch=s[i]-'0';
        if(!trie[p][ch])
            trie[p][ch]=++cnt;
        p=trie[p][ch];
    }
    End[p]++;
}
void Build() {
    queue<int>Q;
    for(int i=0; i<2; i++)
        if(trie[0][i])//预处理第一层
            Q.push(trie[0][i]);
    while(!Q.empty()) {
        int t=Q.front();
        Q.pop();
        for(int i=0; i<2; i++)
            if(trie[t][i]) {//如果不为空
                fail[trie[t][i]]=trie[fail[t]][i];
                End[trie[t][i]]+=End[trie[fail[t]][i]];
                //这里如果在t这里失配但是在t的失配指针匹配,也算t能够匹配
                Q.push(trie[t][i]);
            } else
                trie[t][i]=trie[fail[t]][i];
    }
}
bool DFS(int x,int dep) {
    if(vis[x])return 1;//如果访问过,代表有环
    vis[x]=1;
    bool flag,t=0;
    for(int i=0; i<2; i++) {
        flag=1;
        int id=trie[x][i];//获得下一位置
        if(id&&End[id]>0)flag=0;//如果不为空且存在匹配,不能取
        if(flag) {
            res[0][dep]=i+'0';//构造
            if((flag=DFS(id,dep+1)))return 1;//继续构造,存在环返回
        }
        t|=flag;//记录是否有环
    }
    if(!t&&dep>length) {//无环且比当前字符串长度长
        length=dep;
        for(int j=0; j<length; j++)res[1][j]=res[0][j];
    }
    vis[x]=0;
    return flag;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n;
    for(int i=1; i<=n; i++) {
        cin >>s;
        Insert();//字典树插入
    }
    Build();//建立AC自动机
    if(DFS(0,0))//搜索字符串
        cout <<-1;
    else
        for(int i=0; i<length; i++)cout <<res[1][i];//输出结果
    return 0;
}

参考文献

  1. Colourful Graph 2016香港
  2. Asia Hong Kong Regional Contest 2016
  3. Asia Hong Kong Regional Contest 2016 J Taboo(level 3)(ac自动机+dfs/dp)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值