CF趣题[?]

吐槽

怎么一天只能发十篇啊。[虽然一天写不满十篇但是就是要槽一槽]


codeforces 557E(回文串dp)

题意

分析

定义dp[i][j] 表示[i,j]是否是一个半回文。
if (i+2<=j-2)dp[i][j]=dp[i+2][j-2]&(s[i]==s[j])
然后全都丢进tire树里,通过trie树上二分这一操作来得到字典序k大的串。

值得注意的是,如果有一些串可以合并起来一起加进去的话的(简单来说就是一为另一的前缀)那么就一起加会比较省时间。

code

#include<bits/stdc++.h>
using namespace std;
bool dp[5005][5005];
int n,k,num[5005];
char s[5005];
int to[20000005][2],sum[20000005],cnt[20000005],tot;
struct trie{
    inline void pt(int p,int k){
        for (;;){
            if (cnt[p]>=k){
                putchar('\n');
                return;
            }
            k-=cnt[p];
            if (to[p][0]&&sum[to[p][0]]>=k){
                putchar('a');
                p=to[p][0];
            }
            else{
                if (to[p][0])k-=sum[to[p][0]];
                putchar('b');
                p=to[p][1];
            }
        }
    }
    inline void add(int x,int num){
        int p=0;
        for (int i=x;i<=n;i++){
            if (!to[p][s[i]-'a']){
                to[p][s[i]-'a']=++tot;
            }
            p=to[p][s[i]-'a'];
            sum[p]+=num;
            if (dp[x][i]){
                num--;
                cnt[p]++;
            }
        }
    }
}T;
int main(){
//  freopen("1.in","r",stdin);
    scanf("%s",s+1);
    n=strlen(s+1);
    scanf("%d",&k);
    for (int len=1;len<=n;len++){
        for (int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            if (i+2<=j-2)dp[i][j]=(dp[i+2][j-2]&(s[i]==s[j]));
            else dp[i][j]=(s[i]==s[j]);
            num[i]+=dp[i][j];
        }   
    }
    for (int i=1;i<=n;i++)if(num[i])T.add(i,num[i]);
    T.pt(0,k);
    return 0;
}

246E(启发式合并)

题意

对于树上的每一个节点都有一个颜色,给出一些询问求某个点的子树内深度距离他恰好为k的点中颜色种类数量。

分析

简单来说肯定就是离线然后瞎搞吧。
然后就自然而然的想到了启发式合并(dsu on tree[?])然后就是一些普通的技巧了。

每次清空就打时间戳。
重儿子还是要继承的。
然后就没有东西啦。

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define A first
#define B second
#define mp make_pair
#define M 100005
void read(int &x){
    x=0; char c=getchar();
    for (;c<48;c=getchar());
    for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
map<string,int>id;
set< pii >s[M];
struct ed{
    int x,nx;
}e[M];
int res[M],fa[M],ki[M];
struct query{
    int id,dis;
};
vector<query>Q[M];
int ti[M],n,son[M],cnt[M],TIM=1;
int nx[M],ecnt,col[M],tot,de[M],num[M];
void add(int x,int y){
    e[ecnt]=(ed){y,nx[x]};
    nx[x]=ecnt++;
}
void dfs1(int f,int x){
    de[x]=de[f]+1; cnt[x]=1; 
    for (int i=nx[x];~i;i=e[i].nx){
        dfs1(x,e[i].x);
        cnt[x]+=cnt[e[i].x];
        if (cnt[son[x]]<cnt[e[i].x]||son[x]==0)son[x]=e[i].x;
    }
}
void uni(int x,int y){
    pii tmp;
    set< pii >::iterator it;
    for (it=s[y].begin();it!=s[y].end();++it){
        tmp=*it;
        if (s[x].find(tmp)==s[x].end()){
            if (ti[tmp.A]!=TIM){
                ti[tmp.A]=TIM; num[tmp.A]=0;
            }
            num[tmp.A]++;
            s[x].insert(tmp);
        }
    }
    s[y].clear();
}
void dfs2(int f,int x){
    TIM++;
    for (int i=nx[x];~i;i=e[i].nx)if(e[i].x!=son[x]){
        dfs2(x,e[i].x);
        TIM++;
    }

    if (son[x]){
        dfs2(x,son[x]);
        ki[x]=ki[son[x]];
    }
    else {
        ki[x]=x;
    }

    for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=son[x]){
        uni(ki[x],ki[e[i].x]);
    }
    s[ki[x]].insert(mp(de[x],col[x]));
    if (ti[de[x]]!=TIM)num[de[x]]=0,ti[de[x]]=TIM; num[de[x]]++;
    for (int i=0;i<Q[x].size();i++){
        if (Q[x][i].dis<=n+5&&ti[Q[x][i].dis]==TIM)res[Q[x][i].id]=num[Q[x][i].dis];
    }
}
int main(){
//  freopen("1.in","r",stdin);
    int x,y,dis,m;
    read(n);
    string ss;
    memset(nx,-1,sizeof(nx));
    for (int i=1;i<=n;i++){
        cin>>ss; read(fa[i]);
        if (id[ss])col[i]=id[ss];
        else col[i]=id[ss]=++tot;
        add(fa[i],i);
    }
    dfs1(0,0);
    read(m);
    for (int i=1;i<=m;i++){
        read(x); read(dis);
        Q[x].push_back((query){i,de[x]+dis});
    }
    dfs2(0,0);
    for (int i=1;i<=m;i++)printf("%d\n",res[i]);
    return 0;
}

631E(斜率优化dp)

题意

略…

分析

简单来说就是推一个表达式,发现是直线的形式,然后通过类似斜率优化dp的方式来得到答案。
res=sum[n]-a[i]*i+a[i]*j-sum[j]+sum[i]
res=sum[n]-a[i]*i+sum[i] +a[i]*j-sum[j]
max(a[i]*j-sum[j])
res=sum[n]-a[i]*i+a[i]*j-sum(j,i-1)
res=sum[n]-a[i]*i+sum[i] +a[i]*j-sum[j]
max(a[i]*j-sum[j])
需要考虑的是 对于i,j的位置需要分类,然后才能得到答案。


231E(仙人掌初尝试)

题意

给一个仙人掌图,求2^某个点到另外一個点的路径上环的个数。

分析

这道题目的仙人掌保证一个点只在一个环里面出现,总之还是很舒服的。
考虑把点缩到环里面,那么图就变成了一棵树。
然后就是树上最短路径了。(其实就是求个LCA就解决了)

code

#include<bits/stdc++.h>
#define pb push_back
#define mo 1000000007
using namespace std;
void read(int &x){
    x=0; char c=getchar();
    for (;c<48;c=getchar());
    for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
}
#define M 500005
vector<int>E[M];
int n,num[M];
void Min(int &x,int y){
    if (x>y)x=y;
}
void Max(int &x,int y){
    if (x<y)x=y;
}
int col[M];
struct Tarjan{
    int top,colcnt,de[M],low[M],dfn[M],tot;
    bool vis[M],cut[M<<1];
    void init(){
        memset(nx,-1,sizeof(nx)); ecnt=0;
    }
    struct ed{
        int x,nx;
    }e[M<<1];
    int nx[M],ecnt;
    void add(int x,int y){
        e[ecnt]=(ed){y,nx[x]};
        nx[x]=ecnt++;
    }
    int dfs(int f,int x){
        dfn[x]=low[x]=++tot;        
        for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=f){
            if (!dfn[e[i].x]){
                Min(low[x],dfs(x,e[i].x));
                if (low[e[i].x]>dfn[x])cut[i]=cut[i^1]=1;
            }
            else{
                Min(low[x],dfn[e[i].x]);
            }
        }
        return low[x];
    }
    int color(int f,int x,int Col){
        int cnt=1;
        col[x]=Col;
        for (int i=nx[x];~i;i=e[i].nx)if (e[i].x!=f&&!cut[i]&&!col[e[i].x])cnt+=color(x,e[i].x,Col);
        return cnt;
    }
    void solve(){
        top=0; colcnt=0;
        dfs(1,1);
        for (int i=1;i<=n;i++)if (!col[i]){
            ++colcnt;
            num[colcnt]=color(i,i,colcnt);
            if (num[colcnt]>=2)num[colcnt]=1;
            else num[colcnt]=0;
        }
        for (int i=1;i<=n;i++){
            for (int j=nx[i];~j;j=e[j].nx)if (cut[j]){
                E[col[i]].pb(col[e[j].x]);
            }
        }
    }
}tarjan;
int fa[M],dis[M],cnt[M],son[M],de[M],top[M];
void dfs1(int f,int x){
    dis[x]=dis[f]+num[x];
    cnt[x]=1; fa[x]=f; de[x]=de[f]+1;
    for (int i=0;i<E[x].size();i++)if (E[x][i]!=f){
        dfs1(x,E[x][i]);
        cnt[x]+=cnt[E[x][i]];
        if (cnt[E[x][i]]>cnt[son[x]])son[x]=E[x][i];
    }
}
void dfs2(int f,int x){
    top[x]=f;
    if (son[x])dfs2(f,son[x]);
    for (int i=0;i<E[x].size();i++)if (E[x][i]!=fa[x]&&E[x][i]!=son[x])dfs2(E[x][i],E[x][i]);
}
int lca(int x,int y){
    for (;top[x]!=top[y];){
        if (de[top[x]]>de[top[y]])x=fa[top[x]];
        else y=fa[top[y]];
    }
    if (de[x]<de[y])return x; return y;
}
int po[M];
int main(){
//  freopen("1.in","r",stdin);
//  freopen("1.out","w",stdout);
    int m,x,y,q;
    read(n); read(m);
    tarjan.init();
    for (int i=1;i<=m;i++){
        read(x); read(y);
        tarjan.add(x,y); tarjan.add(y,x);
    }
    tarjan.solve();
    dfs1(1,1);
    dfs2(1,1);
    read(q);
    po[0]=1;
    for (int i=1;i<=n;i++)po[i]=(po[i-1]*2ll)%mo;
    for (int i=1;i<=q;i++){
        read(x); read(y);
        int l=lca(col[x],col[y]);
        printf("%d\n",po[dis[col[x]]+dis[col[y]]-2*dis[l]+num[l]]);
    }   
    return 0;
}

总结

总之能想出这道题的解法还是非常神奇的。
似乎在通过资料了解的仙人掌图和这个并不大一样。
然而对于这样的一种仙人掌,可以通过tarjan求出所有的割边然后把这些边切断,之后在对所有的环染色,缩成一个点。
然后对于普通的仙人掌可以考虑看看这个


799E(PQ强搞太烦线段树万岁)

题意

有n个物品,购买物品i需要花费ci的代价。Arkady和Masha分别有喜欢的物品。
现在需要从中选m个,使得这m个物品中至少有k个Arkady喜欢的物品,k个Masha喜欢的物品。
输出满足要求的最小代价,无解输出-1。

分析

首先发现可以考虑有多少个两个人都喜欢的物品被选中,那么也就能知道只有A喜欢的至少要多少个,只有B喜欢的至少要多少个,以及剩下的所有物品中至少要选择多少个。[可以发现能包涵所有情况]
那么维护几个堆就能解决这个问题。
但是实现起来,各个堆之间的元素要丢来丢去非常麻烦,考虑换一种方式来求。
同样是枚举AB都喜欢的有x个,确定A喜欢的k-x个,确定B喜欢k-x个,那么剩下m+x-2*k在剩下的所有物品里面取。
首先对所有物品排序,建一棵线段树,维护区间内可以选择的物品数量和价值总和。
然后调出AB喜欢,A喜欢,B喜欢的东西,然后分别排序。
之后枚举AB都喜欢的物品的个数,然后贪心的选择即可,把选择了的东西从线段树中删去,最后再找线段树前m+x-2*k大个物品的数值和就好了。[细节就变少了]


467E(单调栈+找性质)

题意

对于一个序列a,要求在里面找到最长的子序列s,满足
1.序列长度=4*k
2.对于任意的(0<=i < k) si4+1=si4+3si4+2=si4+4
输出这个序列。

分析

一开始的想法是线段树。然而可以发现如果是连续四个相同的数字的情况就并不好解决了。


144E(找性质+线段树天下第一)

题意

分析

一个点最多一直往上走或者一直往左走。
那么一个点(x,y)对应的能走到对应一段连续的区间[n-x+1,y],那么其实就是能在区间中间放一个点,然后考虑最多能放入多少个点。
感觉上是传统的贪心,但是由于忘了怎么写于是用线段树写了一波。


313E(玄妙贪心)

题意

分析

首先把两个东西排序。然后从大到小考虑a[i],在b中找出一个b[j] 使得a[i]+b[j]

263E(略恶心的dp)

分析

可以发现所有f(x,y)是四个三角形+a[x][y]*k
然后这样的话预处理一下就出来了。

四个三角形可以通过旋转网格的方式来轻松解决,最后加在f数组里就好了。


257E(超恶心的模拟)

题意

就是如果某一个时间在上面楼层等待的人的数量+当前电梯中想要上楼的人的数量的和>=在下面楼层等待的人的数量+当前电梯中想要下楼的人数量的和
那么电梯往上走。否则电梯往下走。
特别的,如果没有人等待也没有人在电梯里,电梯不动。

分析

感觉就是非常槽心的模拟题。
首先给出一个总的框架。

首先先是四个结构体。
wup:
在楼上等待的一波人,每次询问要求最靠下的楼层是哪一层
支持添加和删除
up:
当前电梯中向上走的一波人,每次询问要求最靠下的楼层是哪一层
支持添加和删除
down:
当前电梯中向下走的一波人,每次询问要求最靠上的楼层是哪一层
支持添加和删除
wdown
在楼下等待的一波人,每次询问最靠上的楼层是哪一层
支持添加和删除

当前的时间是tim,电梯的位置是pos

然后是主要的框架。

void add(int i,int pos){
if (st[i]==pos){
if (to[i]

code

https://paste.ubuntu.com/p/ggyS4cnhT8/

阅读更多
想对作者说点什么? 我来说一句

程序员的算法趣题.pdf

2017年11月10日 7.68MB 下载

程序员的算法趣题【试读】pdf

2017年07月18日 12.43MB 下载

训练逻辑思维的100道趣题

2008年11月02日 3.15MB 下载

没有更多推荐了,返回首页

不良信息举报

CF趣题[?]

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭