bzoj 4822~4824 CQOI2017题解

老C的任务

题目大意:

维护一个二维平面,初始给出一些点及其权.多次询问某个矩形内的权和.
n,m <= 100000

题解:

签到题.
CDQ水一水.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
    x=0;static char ch;static bool flag;flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=(x<<1)+(x<<3)+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 100010;
const int maxm = maxn;
struct Node{
    int x,y;
    int type,val,id;
    Node(){}
    Node(const int &a,const int &b,
     const int &c,const int &d,const int &e){
        x = a;y = b;type = c;val = d;id = e;
    }
}q[maxn*6];
#define lowbit(x) (x&-x)
int n,m,mxlim;
ll c[maxn*6];int vis[maxn*6],T;
inline void modify(int x,ll d){
    for(;x <= mxlim;x += lowbit(x)){
        if(vis[x] == T) c[x] += d;
        else c[x] = d,vis[x] = T;
    }
}
inline ll query(int x){
    ll ret = 0;
    for(;x;x-=lowbit(x)) if(vis[x] == T) ret += c[x];
    return ret;
}
int qcnt = 0;ll ans[maxm];
void solve(int l,int r){
    if(l == r) return ;
    int mid = l+r >> 1;
    solve(l,mid);solve(mid+1,r);
    ++ T;rg i = l,j = mid+1,k = l;
    static Node tmp[maxn*6];
    while(i <= mid || j <= r){
        if(i > mid || (j <= r && q[j].x < q[i].x)){
            if(q[j].type == 2){
            ans[q[j].id] += query(q[j].y)*q[j].val;
            }
            tmp[k++] = q[j++];
        }else{
            if(q[i].type == 1) modify(q[i].y,q[i].val);
            tmp[k++] = q[i++];
        }
    }
    rep(i,l,r) q[i] = tmp[i];
    return ;
}
struct num{
    int x,y,val;
}p[maxn];
struct number{
    int x1,y1,x2,y2;
}nu[maxm];
int b[maxn*6],cnt;
int main(){
    read(n);read(m);
    rg x,y,v;
    rep(i,1,n){
        read(p[i].x);read(p[i].y);read(p[i].val);
        b[++cnt] = p[i].y;
    }
    rg xx,yy;
    rep(i,1,m){
        read(x);read(y);read(xx);read(yy);
        b[++cnt] = y;b[++cnt] = yy;
        if(x > xx) swap(x,xx);
        if(y > yy) swap(y,yy);
        nu[i].x1 = x;nu[i].y1 = y;
        nu[i].x2 = xx;nu[i].y2 = yy;
    }
    sort(b+1,b+cnt+1);
    rep(i,1,n){
        p[i].y = lower_bound(b+1,b+cnt+1,p[i].y) - b;
        x = p[i].x;y = p[i].y;v = p[i].val;
        q[++qcnt] = Node(x,y,1,v,0);
    }
    rep(i,1,m){
        y = lower_bound(b+1,b+cnt+1,nu[i].y1) - b;
        yy = lower_bound(b+1,b+cnt+1,nu[i].y2) - b;
        x = nu[i].x1;xx = nu[i].x2;
        q[++qcnt] = Node(xx,yy,2,1,i);
        q[++qcnt] = Node(x-1,yy,2,-1,i);
        q[++qcnt] = Node(xx,y-1,2,-1,i);
        q[++qcnt] = Node(x-1,y-1,2,1,i);
    }
    mxlim = cnt;
    solve(1,qcnt);
    rep(i,1,m){
        printf("%lld\n",ans[i]);
    }
    return 0;
}

题目: 老 C 的方块

题目大意:

给定一个这样的R行C列网格图:加粗边称为特殊边
1055088-20170415082114298-1913250283.jpg
定义若出现下列情况本质相同的情况则不合法.
1055088-20170415082134580-1842134693.jpg
现在给定网格图内哪些格子是存在的及其被删除的代价共n个
最小化使得网格图合法的代价.
R,C,n <= 100000

题解:

观察所有不合法的状态.
我们发现其实就是特殊边两端不能都有大小超过2的四连块.(不经过特殊边)
所以我们可以列出一些关系:
如果将题目中给出的网格图的行列看成横纵坐标轴:(即(1,1)和(2,1)之间为特殊边)
我们可以得到这样的关系:

对于特殊边(3,2) - (4,2)来说.
如果要这条特殊边合法,那么必须是两边不能同时含有大小超过2的四连块.
那么如果我们删除(3,2)或者(4,2)这是一定满足的.
下面就是不删除(3,2)或者(4,2)的情况,那么我们知道:
要么全部删除(3,1),(2,2),(3,3),要么全部删除(4,1),(5,2),(4,3).
然后我们发现我们可以对网格图进行三色染色:建立最小割模型.
假设我们将(3,1),(2,2),(3,3)染成黑色那么(4,1),(5,2),(4,3)一定是白色.
然后将所有在特殊边两边的点染成黄色.
可以建立最小割模型:

1055088-20170415082150330-1618072708.png

蓝色边为删除对应格子的代价,黑色均为inf,黄色边为特殊边两边的各自的代价的最小值.
跑最小割即可.

#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
    x=0;char ch;bool flag = false;
    while(ch=getchar(),ch<'!'); if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
typedef pair<int,int> pa;
const int maxn = 100010;
const int inf = 0x3f3f3f3f;
struct Edge{
    int to,next,cap;
}G[maxn*10];
int head[maxn],cnt = 1;
void add(int u,int v,int c){
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
    G[cnt].cap = c;
}
inline void insert(int u,int v,int c){
    add(u,v,c);add(v,u,0);
}
#define v G[i].to
int dis[maxn],q[maxn],l,r,S,T;
bool bfs(){
    memset(dis,-1,sizeof dis);
    l = 0;r = -1;q[++r] = S;
    dis[S] = 0;
    while(l <= r){
        int u = q[l++];
        for(int i = head[u];i;i=G[i].next){
            if(dis[v] == -1 && G[i].cap != 0){
                dis[v] = dis[u] + 1;
                q[++r] = v;
            }
        }
    }return dis[T] != -1;
}
int dfs(int u,int f){
    if(u == T || f == 0) return f;
    int ret = 0;
    for(int i = head[u];i;i=G[i].next){
        if(dis[v] == dis[u] + 1 && G[i].cap){
            int x = dfs(v,min(f,G[i].cap));
            f -= x;ret += x;
            G[i].cap -= x;
            G[i^1].cap += x;
            if(f == 0) break;
        }
    }return ret;
}
inline int dinic(){
    int ret = 0;
    while(bfs()) ret += dfs(S,inf);
    return ret;
}
#undef v
inline bool ca(int x,int y){
    int id = (x + 1) >> 1;
    if(id & 1) return (y&1) == 1;
    else return (y & 1) == 0;
}
inline bool cb(int x,int y){
    int id = (x + 1) >> 1;
    if(id & 1){
        if((y & 1) == 1) return false;
        return (x & 1) == 1;
    }else{
        if((y & 1) == 0) return false;
        return (x & 1) == 0;
    }
}
inline bool cc(int x,int y){
    int id = (x + 1) >> 1;
    if(id & 1){
        if((y & 1) == 1) return false;
        return (x & 1) == 0;
    }else{
        if((y & 1) == 0) return false;
        return (x & 1) == 1;
    }
}
int d1[3][2] = {{-1,0},{0,1},{0,-1}};
int d2[3][2] = {{1,0},{0,1},{0,-1}};
map<pa,int>mp;
struct Node{
    int x,y,w;
}p[maxn];
int main(){
    int C,R,n;read(C);read(R);read(n);
    S = maxn - 5;T = S + 1;
    rep(i,1,n){
        read(p[i].x);read(p[i].y);read(p[i].w);
        mp[make_pair(p[i].x,p[i].y)] = i;
        if(cb(p[i].x,p[i].y)) insert(S,i,p[i].w);
        if(cc(p[i].x,p[i].y)) insert(i,T,p[i].w);
    }
    rep(i,1,n){
        if( (p[i].x & 1) && ca(p[i].x,p[i].y) 
            && (mp.count(make_pair(p[i].x+1,p[i].y)))){
            rg j = mp[make_pair(p[i].x+1,p[i].y)];
            if(p[i].y & 1){
                insert(i,j,min(p[i].w,p[j].w));
                rep(k,0,2){
                    rg nx = p[i].x + d1[k][0];
                    rg ny = p[i].y + d1[k][1];
                    if(!mp.count(make_pair(nx,ny))) continue;
                    insert(mp[make_pair(nx,ny)],i,inf);
                }
                rep(k,0,2){
                    rg nx = p[j].x + d2[k][0];
                    rg ny = p[j].y + d2[k][1];
                    if(!mp.count(make_pair(nx,ny))) continue;
                    insert(j,mp[make_pair(nx,ny)],inf);
                }
            }else{
                insert(j,i,min(p[i].w,p[j].w));
                rep(k,0,2){
                    rg nx = p[i].x + d1[k][0];
                    rg ny = p[i].y + d1[k][1];
                    if(!mp.count(make_pair(nx,ny))) continue;
                    insert(i,mp[make_pair(nx,ny)],inf);
                }
                rep(k,0,2){
                    rg nx = p[j].x + d2[k][0];
                    rg ny = p[j].y + d2[k][1];
                    if(!mp.count(make_pair(nx,ny))) continue;
                    insert(mp[make_pair(nx,ny)],j,inf);
                }
            }
        }
    }
    int ans = dinic();
    printf("%d\n",ans);
    return 0;
}

老 C 的键盘

题目大意:

有一个n的排列对于每个位置i,给出[i/2]与i位置上的数的大小关系,求可能的排列数.
n <= 100

题解:

不难发现实际上这是一棵树.
我们把这棵树建出来,那么现在变成一给定一棵树,树上的边代表的父子的大小关系.
我们考虑树归 : 设\(f[i][j]\)表示在以i为根的子树内根作为排名为j的值出现的排列数.
那么问题就在于状态的合并更新了.
对于一个\(f[u][]\)要将儿子的状态\(f[v][]\)合并进来,我们首先分情况讨论:

  • u的值必须 > v的值:
    我们想象成两个排列进行合并.
    我们知道我们必须满足的条件即v在u的前面.
    那么我们枚举一下需要有多少排在u的前面即可.
    由于需要保证相对顺序不发生变化,所以将n个数插到m个数中的方案即:\(C_{n+m-1}^m\)
    但是由于这里要么前面能插,要么后面能插,即什么时候都多一个可以插的位置,
    所以实际上这里插入的方案数为\(C_{n+m}^m\)
    这样我们列出转移方程:
    \[f[u][j+k] += C_{j-1+k}^{k}C_{s_u-j+s_v-k}^{s_v-k}*f[u][j]\sum_{i=1}^kf[v][i]\]

  • u的值必须 < v的值:
    相应地列出方程:
    \[f[u][j+k-1] += C_{j-1+k-1}^{k-1}C_{s_u-j+s_v-(k-1)}^{s_u-j}*f[u][j]\sum_{i=k}^nf[v][i]\]

预处理前缀后缀和可以将sigma消掉.

据说这么做复杂度是\(O(n^2\log n)\)的..我也不知道为什么..

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
    x=0;static char ch;static bool flag;flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=(x<<1)+(x<<3)+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 128;
const int mod = (1e9 + 7);
struct Edge{
    int to,next,type;
}G[maxn<<1];
int head[maxn],cnt;
void add(int u,int v,int d){
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
    G[cnt].type = d;
}
int C[maxn][maxn];
inline void prework(int n){
    C[0][0] = 1;
    rep(i,1,n) rep(j,0,n){
        C[i][j] = C[i-1][j];
        if(j != 0) C[i][j] += C[i-1][j-1];
        if(C[i][j] >= mod) C[i][j] -= mod;
    }
}
int f[maxn][maxn],pre[maxn][maxn],suf[maxn][maxn];
inline void init(){
    memset(head,0,sizeof head);
    cnt = 0;
}
int siz[maxn],n;
#define v G[i].to
void dfs(int u,int fa){
    siz[u] = 1;
    for(int i = head[u];i;i=G[i].next){
        if(v == fa) continue;
        dfs(v,u);siz[u] += siz[v];
    }
    rep(i,2,n) f[u][i] = 0;
    f[u][1] = 1;
    rg s = 1;
    static int g[maxn];
    for(int i = head[u];i;i=G[i].next){
        if(v == fa) continue;
        rep(j,1,s+siz[v]) g[j] = 0;
        if(G[i].type == -1){
            rep(j,1,s) rep(k,1,siz[v]){
                g[j+k-1] += 1LL*C[j+k-2][k-1]*C[s-j+siz[v]-k+1][s-j]%mod*f[u][j]%mod*suf[v][k]%mod;
                if(g[j+k-1] >= mod) g[j+k-1] -= mod;
            }
        }else{
            rep(j,1,s) rep(k,1,siz[v]){
                g[j+k] += 1LL*C[j+k-1][k]*C[s-j+siz[v]-k][s-j]%mod*f[u][j]%mod*pre[v][k]%mod;
                if(g[j+k] >= mod) g[j+k] -= mod;
            }
        }
        rep(j,1,s + siz[v]) f[u][j] = g[j];
        s += siz[v];
    }
    rep(i,1,n){
        pre[u][i] = pre[u][i-1] + f[u][i];
        if(pre[u][i] >= mod) pre[u][i] -= mod;
    }
    per(i,n,1){
        suf[u][i] = suf[u][i+1] + f[u][i];
        if(suf[u][i] >= mod) suf[u][i] -= mod;
    }
    return ;
}
#undef v
inline void work(){
    init();read(n);
    rg u,v;char ch;
    rep(i,2,n){
        u = i>>1;
        while(ch=getchar(),ch<'!');
        v = i;
        if(ch == '<') add(u,v,-1),add(v,u,1);
        else add(u,v,1),add(v,u,-1);
    }
    dfs(1,1);
    int ans = 0;
    rep(i,1,n){
        ans += f[1][i];
        if(ans >= mod) ans -= mod;
    }
    printf("%d\n",ans);
}
int main(){
    int T;T = 1;
    prework(100);
    while(T--) work();
    return 0;
}

转载于:https://www.cnblogs.com/Skyminer/p/6713011.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值