[题解]BJOI2014

想法

题意简述

m 个想法n道题目 (mn)
每个题目可以表示为想法的集合。
m 道题目中,第i道题目的想法是 {i}
给出剩余 nm 道题目是由哪两道题目拼成的,它的想法集合是拼成它的题目的并集。
求每道题目的想法集合的大小

数据范围

1m105
1n106

评分方法

对于每个输出文件
如果其中你有 95% 以上的行的答案和正确答案的误差不超过 25% ,那么你就可以得到分数。
所谓误差不超过 25% ,即,如果正确答案是 X ,那么你的答案在[0.8X,1.25X]这个闭区间内。

思路

好神的题目。
对每个题目随机rand一个值。
拓扑排序,DP出每个集合前 t 小的数。
每个点满足等式ansit=RANDMAXsett
RANDMAX表示rand的值域上界。
t 越大正确的概率就越大。
为什么是对的呢?
我YY了一下。
随机值期望是平均分布的,每隔RANDMAXt就有一个值。
如果是这样的话就显然了恩……
这样就可以A了。

代码

#include<cstdio>
#include<cstring>
#include<vector>
#include<cstdlib>
using namespace std;
#define N 1000010
#define lim 20
int n,m;
int ch[N][2];
int val[N];
vector<int> vec[N];
void merge(int node)
{
    int sa=vec[ch[node][0]].size();
    int sb=vec[ch[node][1]].size();
    int nowa=0,nowb=0;
    for (int i=1;i<=lim&&(nowa<sa||nowb<sb);i++)
    {
        if (nowa==sa)
            vec[node].push_back(vec[ch[node][1]][nowb++]);
        else if (nowb==sb)
            vec[node].push_back(vec[ch[node][0]][nowa++]);
        else if (vec[ch[node][0]][nowa]<vec[ch[node][1]][nowb])
            vec[node].push_back(vec[ch[node][0]][nowa++]);
        else if (vec[ch[node][0]][nowa]>vec[ch[node][1]][nowb])
            vec[node].push_back(vec[ch[node][1]][nowb++]);
        else
        {
            vec[node].push_back(vec[ch[node][0]][nowa++]);
            nowb++;
        }
    }
}
int main()
{
    srand(20000428);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        val[i]=rand()+1;
    for (int i=m+1;i<=n;i++)
        scanf("%d%d",&ch[i][0],&ch[i][1]);
    for (int i=1;i<=m;i++)
        vec[i].push_back(val[i]);
    for (int i=m+1;i<=n;i++)
        merge(i);
    for (int i=m+1;i<=n;i++)
        if (vec[i].size()<lim)
            printf("%d\n",vec[i].size());
        else
            printf("%d\n",(int)(1.0*lim*RAND_MAX/vec[i][lim-1]));
    return 0;   
}

大融合

题意简述

一开始有 n 个孤立的点。
后面有q个操作。
操作有两种类型:
1.将不连通的两个点连接起来。
2.问两个点之间的边(保证存在)有多少点对的路径经过。

数据范围

1n,q105

思路

离线+树链剖分+线段树+并查集。
先离线,找出最终形态的森林。
我们可以发现,每个询问一定是父亲和儿子之间的边。
那么答案就是 sizeson(sizeblocksizeson)
动态维护每个点的size。
每次我们加边的影响的点,即为父亲一直沿存在的边往上的所有点。
可以用并查集找到最上面的点。
这样就变成了区间加,可以用树链剖分+线段树实现。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define N 100010
#define Q 100010
struct edge{
    int s,t,next;
}e[N<<1];
int head[N],cnt;
void addedge(int s,int t)
{
    e[cnt].s=s;e[cnt].t=t;e[cnt].next=head[s];head[s]=cnt++;
    e[cnt].s=t;e[cnt].t=s;e[cnt].next=head[t];head[t]=cnt++;
}
struct modi{
    int s,t;
    bool ty;
}mo[Q];
int dfn[N],size[N],deep[N],son[N],fa[N],seq[N],top[N];
bool vis[N];
int n,q,u,v,order;
namespace dsu
{
    int fa[N];
    void Init()
    {
        for (int i=1;i<=n;i++)
            fa[i]=i;
    }
    int getfa(int x)
    {
        if (fa[x]!=x)
            fa[x]=getfa(fa[x]);
        return fa[x];
    }
    void unionn(int x,int y)
    {
        fa[x]=y;
    }
}
namespace Segtree
{
    struct Node{
        int val,add;
        Node()
        {
            val=add=0;
        }
        void modify_add(int x)
        {
            val+=x;
            add+=x;
        }
    }tree[N<<2];
    void build(int l,int r,int node)
    {
        if (l==r)
        {
            tree[node].val=seq[l];
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,node<<1);
        build(mid+1,r,node<<1|1);
    }
    void pushdown(int node)
    {
        if (tree[node].add)
        {
            tree[node<<1].modify_add(tree[node].add);
            tree[node<<1|1].modify_add(tree[node].add);
            tree[node].add=0;
        }
    }
    void modify_add(int L,int R,int l,int r,int node,int val)
    {
        if (L<=l&&r<=R)
        {
            tree[node].modify_add(val);
            return;
        }
        pushdown(node);
        int mid=(l+r)>>1;
        if (L<=mid)
            modify_add(L,R,l,mid,node<<1,val);
        if (R>mid)
            modify_add(L,R,mid+1,r,node<<1|1,val);
    }
    void solve_add(int u,int v,int val)
    {
        int tu=top[u],tv=top[v];
        while (tu!=tv)
        {
            if (deep[tu]<deep[tv])
                swap(tu,tv),swap(u,v);
            modify_add(dfn[tu],dfn[u],1,n,1,val);
            u=fa[tu];
            tu=top[u];
        }
        if (deep[u]>deep[v])
            swap(u,v);
        modify_add(dfn[u],dfn[v],1,n,1,val);
    }
    int query(int pos,int l,int r,int node)
    {
        if (l==r)
            return tree[node].val;
        pushdown(node);
        int mid=(l+r)>>1;
        if (pos<=mid)
            return query(pos,l,mid,node<<1);
        else
            return query(pos,mid+1,r,node<<1|1);
    }
}
bool read()
{
    char ch=getchar();
    while (ch!='A'&&ch!='Q')
        ch=getchar();
    return ch=='Q';
}
void dfs(int node,int lastfa,int de)
{
    vis[node]=1;
    size[node]=1;
    deep[node]=de;
    son[node]=0;
    fa[node]=lastfa;
    for (int i=head[node];i!=-1;i=e[i].next)
        if (e[i].t!=lastfa)
        {
            dfs(e[i].t,node,de+1);
            size[node]+=size[e[i].t];
            if (size[e[i].t]>size[son[node]])
                son[node]=e[i].t;
        }
}
void dfs2(int node,int tp)
{
    dfn[node]=++order;
    seq[order]=1;
    top[node]=tp;
    if (son[node])
        dfs2(son[node],tp);
    for (int i=head[node];i!=-1;i=e[i].next)
        if (!dfn[e[i].t])
            dfs2(e[i].t,e[i].t);
}
int main()
{
    scanf("%d%d",&n,&q);
    memset(head,0xff,sizeof(head));
    cnt=0;
    for (int i=1;i<=q;i++)
    {
        mo[i].ty=read();
        scanf("%d%d",&mo[i].s,&mo[i].t);
        if (!mo[i].ty)
            addedge(mo[i].s,mo[i].t);
    }
    for (int i=1;i<=n;i++)
        if (!vis[i])
            dfs(i,i,1);
    for (int i=1;i<=n;i++)
        if (!dfn[i])
            dfs2(i,i);
    Segtree::build(1,n,1);
    dsu::Init();
    for (int i=1;i<=q;i++)
    {
        if (mo[i].ty)
        {
            u=mo[i].s,v=mo[i].t;
            if (deep[u]<deep[v])
                swap(u,v);
            int x=Segtree::query(dfn[dsu::getfa(v)],1,n,1);
            int y=Segtree::query(dfn[u],1,n,1);
            printf("%lld\n",1LL*(x-y)*y);
        }
        else
        {
            u=mo[i].s,v=mo[i].t;
            if (deep[u]<deep[v])
                swap(u,v);
            dsu::unionn(u,v);
            Segtree::solve_add(dsu::getfa(v),v,Segtree::query(dfn[u],1,n,1));
        }
    }
    return 0;
}

路径

题意简述

n 个点的无向图,每个点有()+-*0123456789之一的字符。
m条边连接。
你要在这张图上走恰好 k 个节点(可以走重复的点和边)
使得形成的表达式合法。
合法的条件:
1.括号配对但不能有空括号
2.加法不能做正号,但减号可以当符号
3.0可以做除数
4.数字不能有多余的前导0
求方案数模109+7的答案。

数据范围

1n20
1mn(n1)/2
1k30

思路

多维DP。
f[a][b][c][d] 表示走了 a 次,当前在b点,左括号比右括号多 c 个,d表示当前是否是前导0。
转移注意细节。

代码

#include<cstdio>
#include<cstring>
using namespace std;
#define N 25
const int p=1000000007;
struct edge{
    int s,t,next;
}e[N*N];
int head[N],cnt;
void addedge(int s,int t)
{
    e[cnt].s=s;e[cnt].t=t;e[cnt].next=head[s];head[s]=cnt++;
    e[cnt].s=t;e[cnt].t=s;e[cnt].next=head[t];head[t]=cnt++;
}
long long f[35][N][35][2];//num,where,(,head0
long long ans;
int n,m,k,u,v;
char st[N];
bool check(char a,char b,bool zero1,bool zero2)
{
    if (zero1)
        if ((b==')')||(b=='+')||(b=='-')||(b=='*')||(b=='/'))
            return true;
        else
            return false;
    else if (zero2)
        if ((a=='(')||(a=='+')||(a=='-')||(a=='*')||(a=='/'))
            return true;
        else
            return false;
    else if ((a=='(')&&((b>='1'&&b<='9')||(b=='-')||b=='('))
        return true;
    else if ((a==')')&&((b=='+')||(b=='-')||(b=='*')||(b=='/')||(b==')')))
        return true;
    else if ((a>='0'&&a<='9')&&((b=='+')||(b=='-')||(b=='*')||(b=='/')||(b==')')||(b>='0'&&b<='9')))
        return true;
    else if ((a=='+'||(a=='-')||(a=='*')||(a=='/'))&&((b>='1'&&b<='9')||(b=='(')))
        return true;
    else
        return false;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    scanf("%s",st+1);
    memset(head,0xff,sizeof(head));
    cnt=0;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    for (int i=1;i<=n;i++)
        if (st[i]=='(')
            f[1][i][1][0]=1;
        else if (st[i]=='0')
            f[1][i][0][1]=1;
        else if (st[i]=='-')
            f[1][i][0][0]=1;
        else if (st[i]>='1'&&st[i]<='9')
            f[1][i][0][0]=1;
    for (int i1=1;i1<k;i1++)
        for (int i2=1;i2<=n;i2++)
        {
            for (int i3=0;i3<=k;i3++)
                for (int i4=0;i4<=1;i4++)
                {
                    for (int i5=head[i2];i5!=-1;i5=e[i5].next)
                    {

                        if (check(st[i2],st[e[i5].t],i4,0))
                            if (st[e[i5].t]=='(')
                                f[i1+1][e[i5].t][i3+1][0]=(f[i1+1][e[i5].t][i3+1][0]+f[i1][i2][i3][i4])%p;
                            else if (st[e[i5].t]==')')
                                if (i3>=1)
                                    f[i1+1][e[i5].t][i3-1][0]=(f[i1+1][e[i5].t][i3-1][0]+f[i1][i2][i3][i4])%p;
                                else;
                            else
                                f[i1+1][e[i5].t][i3][0]=(f[i1+1][e[i5].t][i3][0]+f[i1][i2][i3][i4])%p;

                        if (st[e[i5].t]=='0'&&check(st[i2],st[e[i5].t],i4,1))
                            f[i1+1][e[i5].t][i3][1]=(f[i1+1][e[i5].t][i3][1]+f[i1][i2][i3][i4])%p;
                    }
                    if (st[i2]!='0')
                        break;
                }
        }
    for (int i=1;i<=n;i++)
        if (st[i]!='+'&&st[i]!='-'&&st[i]!='*'&&st[i]!='/')
            ans=(ans+f[k][i][0][0]+f[k][i][0][1])%p;;
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值