UESTC 2016 Summer Training #19 Div.2(未完待续)

来源:互联网 发布:食品加工控制软件 编辑:程序博客网 时间:2024/05/16 06:38

题目来源:
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=643
2014 Asia Jakarta Regional Contest
UVALive 6906 - 6916

A

B
题意:
给出一个哑铃图的定义. 一个图中分成点个数相等的两堆,每堆内部是一个完全图.堆与堆之间只有一条边相连. 现在给出一个图,不一定联通. 看它的每一个联通块是不是我们的哑铃图. 输出满足哑铃图的个数.

题解:
法一:用桥来判断. 哑铃图一定只有一个桥吗?发现只有一个特例,两边是2个点的时候都是桥. 其他情况有且只有一个桥. 那么有一个桥的一定是哑铃图吗?还要求桥的两边点数一样,并且边数是一个定值.用度来统计就好了.
所以就是先dfs tarjan找一下桥,找到桥之后看两边的点数和度数判定.额外的是4个点的特判.
法二:只从度的角度来判断.发现特殊的两个点的度数是比其他的都多1的.并且其他的点的度数都是确定的.那么我们判定一下先仅有两个特殊的度点.然后从其中的一个点,刨去另一个点不能跑,只跑一边去染色,然后先看度满足情况.之后再看每个点是不是只和自己颜色的染过.

///////////代码是判定桥的.#include <cstdio>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int N = 1e3;int n,m;int vis[N];int dfn[N],low[N],in[N];int key[10010],to;vector <int> g[N];int flag = 0;int cnt = 0,cn = 0;int dfs(int u,int f,int len){    cn += in[u];    vis[u] = 1;    low[u] = len;    dfn[u] = len;    int tot = 0;    for (int i = 0;i < g[u].size();i++)    {        int v = g[u][i];        if (v == f) continue;        if (vis[v] == 0)        {            int tmp = dfs(v,u,len+1);            tot += tmp;            if (low[v] > dfn[u])                key[++to] = tmp;            low[u] = min(low[v],low[u]);        }        else            low[u] = min(dfn[v],low[u]);    }    return tot + 1;}int main(){    int t;    scanf("%d",&t);    while (t--)    {        memset(vis,0,sizeof vis);        memset(in,0,sizeof in);        scanf("%d %d",&n,&m);        for (int i = 1;i <= n;i++) g[i].clear();        int ans = 0;        for (int i = 1;i <= m;i++)        {            int x,y;            scanf("%d %d",&x,&y);            in[x]++;in[y]++;            g[y].push_back(x);            g[x].push_back(y);        }        for (int i = 1;i <= n;i++)        if (vis[i] == 0)        {            cnt = cn = to = 0;            cnt = dfs(i,0,1);            if (cnt & 1) continue;            if (cn & 1) continue;            int flag2 = 0;            cnt /= 2;            for (int i = 1;i <= to;i++)                if (key[i] == cnt) flag2 = 1;            if (flag2 == 1 && cn/2 == 1 + cnt*(cnt-1)) ans++;        }        static int ca = 0;        printf("Case #%d: %d\n",++ca,ans);    }}

C
题意:
给定N≤103个障碍物,和一辆车,变挡次数K≤10,车的电量E≤50,车有4挡,耗电为挡数,不同的挡提供不同的额外能量
能量不足以提供车当前挡前进的时候,车直接就报废了。问穿过所有的障碍物人的最少能量花费?
分析:
赤果果的dp
dp[i][k][e][l]:=第几个障碍物,剩余变挡次数,剩余能量,当前挡数,人的最小能量花费,由于状态1000∗10∗50∗4=2e6比较大,乘case数=100就T了
用队列优化一下就好了,类似于spfa的感觉

////  Created by TaoSama on 2015-12-21//  Copyright (c) 2015 TaoSama. All rights reserved.////#pragma comment(linker, "/STACK:1024000000,1024000000")#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <vector>using namespace std;#define pr(x) cout << #x << " = " << x << "  "#define prln(x) cout << #x << " = " << x << endlconst int N = 1e3 + 10, INF = 0x3f3f3f3f, MOD = 1e9 + 7;int n, k, e, ans;int h[N], f[N][11][51][4];bool in[N][11][51][4];int power[] = {0, 4, 8, 11};struct Sta {    int p, c, e, l; //position, chance, energy, level};void see(Sta u, Sta v) {    int &cur = f[u.p][u.c][u.e][u.l];    int &nxt = f[v.p][v.c][v.e][v.l];    printf("f[%d][%d][%d][%d] = %d → ", u.p, u.c, u.e, u.l, cur);    printf("f[%d][%d][%d][%d] = %d\n", v.p, v.c, v.e, v.l, nxt);}void spfa() {    queue<Sta> q;    memset(f, 0x3f, sizeof f);    memset(in, false, sizeof in);    f[0][k][e][0] = 0; in[0][k][e][0] = true;    q.push((Sta) {0, k, e, 0});    while(q.size()) {        Sta u = q.front(); q.pop();        in[u.p][u.c][u.e][u.l] = false;        int &cur = f[u.p][u.c][u.e][u.l];        if(u.p == n) {            ans = min(ans, cur);            continue;        }        if(u.c) {            for(int i = 0; i < 4; ++i) {                if(i == u.l || u.e < i) continue;                Sta v = u;                ++v.p; --v.c; v.e -= i; v.l = i;                int cost = max(0, h[v.p] - power[v.l]);                int &nxt = f[v.p][v.c][v.e][v.l];                if(nxt > cur + cost) {                    nxt = cur + cost;//                  see(u, v);                    if(!in[v.p][v.c][v.e][v.l]) {                        in[v.p][v.c][v.e][v.l] = true;                        q.push(v);                    }                }            }        }        Sta v = u; ++v.p;        if(v.e < v.l) v.c = v.e = v.l = 0; //lack energy, just set 0        else v.e -= v.l;        int cost = max(0, h[v.p] - power[v.l]);        int &nxt = f[v.p][v.c][v.e][v.l];        if(nxt > cur + cost) {            nxt = cur + cost;            if(!in[v.p][v.c][v.e][v.l]) {                in[v.p][v.c][v.e][v.l] = true;                q.push(v);            }        }    }}int main() {#ifdef LOCAL    freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);//  freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);#endif    ios_base::sync_with_stdio(0);    int t; scanf("%d", &t);    while(t--) {        scanf("%d%d%d", &n, &k, &e);        for(int i = 1; i <= n; ++i) scanf("%d", h + i);        ans = INF;        spfa();        static int kase = 0;        printf("Case #%d: %d\n", ++kase, ans);    }    return 0;}

D

/* *  组合数学 *  题意: *      有N(500)个人,编号1~N,忽略前K(1<=K<N)的单调性,求使P(1<=P<=N)为第一个数小于前一个数或者最后一个数的 *      排列方案数。 *  思路: *      根据样例可知,上升序列从第K位开始计算。 *      [   K-1   ][   上升序列   ] P [   任意   ] *      或 *      [   K-1   ][   上升序列,P   ] *      对于排列1,需要枚举上升序列的长度,记为L(1<=L<=N-K), *      因为P小于前一个数,所以L必然含有[P+1,N]的数,记为I(1<=I<=min(N-P,L)) *        那么方案数:∑∑C(N-P,I)*C(P-1,L-I)*P(N-1-L),剩下N-1-L个数任意排列 *        对于排列2,因为后半段是上升序列,所以[P+1,N]必需全部放入K-1中, *        方案数:C(K-1,N-P)*P(N-P)*C(P-1,K-1-(N-P))*P(K-1-(N-P)) */#include<cstdio>#include<cstring>#include<cmath>#include<set>#include<algorithm>using namespace std;#define LL long long#define N 507#define MOD 1000000007LL a[N];LL c[N][N];void init(){    LL i,j;    // A    a[0]=1;    for(i=1;i<N;++i) a[i]=(a[i-1]*i)%MOD;    // C    for(i=0;i<N;++i)    for(j=0;j<=i;++j)    if(j==0 || j==i){        c[i][j]=1;    }    else{        c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;    }}int main(){    int T,tt=0;    int n,m,p;    int i,j;    LL ans;    init();    scanf("%d",&T);    while(T--){        scanf("%d%d%d",&n,&m,&p);        ans=0;        // end        if(n-p<m){//            ans=a[n-p]*c[p-1][m-1-(n-p)]%MOD*a[m-1-(n-p)]%MOD;            ans=c[m-1][n-p]*a[n-p]%MOD*c[p-1][m-1-(n-p)]%MOD*a[m-1-(n-p)]%MOD;        }        // between        for(j=1;j<=n-m;++j){            for(i=1;i<=min(n-p,j);++i){                ans=(ans+c[n-p][i]*c[p-1][j-i]%MOD*a[n-1-j]%MOD)%MOD;            }        }        printf("Case #%d: %lld\n",++tt,ans);    }    return 0;}

E
别人的解法:
【题意】:
给出多棵树和两类操作:操作(C x)删除结点 x 与其父结点的连边;操作(Q a b)询问 a b 是否连通。
【解题思路】:
连通性的查询容易想到用并查集,关键在于如何处理删边。
考虑到删边的难点在于查询时的路径压缩导致某些结点与其父结点”不直接相连”,这里使用离线处理,在查询之前把所有该删的边删除,同时逆序处理询问操作;当逆序处理到删边操作时,复原删掉的边(删除变为增边)。
【代码】:(上了个比较标准的并查集模板)

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<stack>#include<algorithm>#define LL long long#define maxn 25000#define IN freopen("in.txt","r",stdin);using namespace std;struct Union_Find_Set{    int fa[maxn];    /*每个结点的父亲节点编号*/    int rank[maxn];    /*树的高度*/    /*构造并查集并初始化*/    void make_set()    {        for(int i=0; i<maxn; i++){            fa[i] = i;    /*初始时本身构成一个集合,根为本身*/            rank[i] = 0;        }    }    /*递归查找结点所在树的根节点*/    int find_set(int x)    {        /*路径压缩*/        return x!=fa[x]? fa[x]=find_set(fa[x]) : x;    }    /*合并两个集合*/    void unite_set(int x, int y)    {        x = find_set(x);        y = find_set(y);        /*记录树的高度防止合并后退化,rank小的向rank大的连接*/        if(rank[x] < rank[y]) swap(x,y);        fa[y] = x;    /*合并*/        if(rank[x] == rank[y]) rank[x]++;    /*高度相同则加1*/    }    /*判断两结点是否属于同一集合*/    bool same_set(int x, int y)    {        return find_set(x) == find_set(y);    }}UFS;int n,q;struct node{    char type;    int first, second;};stack<node> s;stack<bool> ans;int main(int argc, char const *argv[]){    //IN;    int t,ca=1;scanf("%d",&t);    while(t--)    {        scanf("%d %d",&n,&q);        while(!s.empty()) s.pop();        while(!ans.empty()) ans.pop();        UFS.make_set();        for(int i=1; i<=n; i++){            scanf("%d",&UFS.fa[i]);            if(!UFS.fa[i]) UFS.fa[i] = i;        }        for(int i=1; i<=q; i++)        {            node tmp_node;            getchar();            scanf("%c",&tmp_node.type);            if(tmp_node.type=='Q'){                scanf("%d %d",&tmp_node.first, &tmp_node.second);            }            else{                scanf("%d",&tmp_node.first);                tmp_node.second = UFS.fa[tmp_node.first];                /*离线处理--询问之前删边,避免路径压缩导致删边失效*/                UFS.fa[tmp_node.first] = tmp_node.first;            }            s.push(tmp_node);        }        while(q--)        {            node tmp_node = s.top(); s.pop();            if(tmp_node.type=='Q'){                if(UFS.same_set(tmp_node.first, tmp_node.second)) ans.push(1);                else ans.push(0);            }            else{                UFS.fa[tmp_node.first] = tmp_node.second;            }        }        printf("Case #%d:\n", ca++);        while(!ans.empty())        {            if(ans.top() == 1) puts("YES");            else puts("NO");            ans.pop();        }    }    return 0;}

我的做法:
由于输入只是给了每个点的父亲节点,套用并查集的思想,但是不进行路径压缩,按照题意模拟即可。

#include <bits/stdc++.h>#define _ ios_base::sync_with_stdio(0);cin.tie(0);#define INF 0x3f3f3f3f#define eps 1e-6typedef long long LL;const double pi = acos(-1.0);const long long mod = 1e9 + 9;using namespace std;const int MAX = 20005;int p[MAX];int Find(int x){    return p[x] == x ? x : Find(p[x]);}int main(){    ios_base::sync_with_stdio(false); cin.tie(0);    //freopen("int.txt","r",stdin);    //freopen("out.txt","w",stdout);    int cnt = 0;    int T;    cin >> T;    while(T--)    {        printf("Case #%d:\n",++cnt);        int N,K;        cin >> N >> K;        int x;        for(int i = 1;i <= N;i++)        {            cin >> x;            if(x == 0)                p[i] = i;            else                p[i] = x;        }        char s[5];        for(int i = 0;i < K;i++)        {            cin >> s;            int a,b;            if(s[0] == 'Q')            {                cin >> a >> b;                int u = Find(a);                int v = Find(b);                if(u != v)                    puts("NO");                else                    puts("YES");            }            else            {                cin >> x;                p[x] = x;            }        }    }    return 0;}

F

G

/* *    数论 *    题意: *        给N(<=1000)盏灯,和K(<=1000)个质数。起初每盏灯都是暗的,每个质数可以改变位置是其倍数的灯的状态(暗变亮、亮 *        变暗),求最多能点亮多少盏灯。 *    思路: *        如果两个质数会改变同一盏灯,那么满足a*b<=n。 *        通过这个式子,可以发现,如果质数x和y均大于sqrt(n),那么x、y不会冲突(就是x选了,选y不会影响x点的灯)。 *        而N才1000,[sqrt(N)] = 31, *        不大于31的质数:2、3、5、7、11、13、17、19、23、29、31共11个。 *        对于大于sqrt(n)的数,取不取该数取决于小于sqrt(n)的数。 *        所以对于前11个质数,枚举其状态(取或不取)。 *        剩下的数则根据取之后会不会使答案更优来决定其状态。 */#include<cstdio>#include<cstring>#include<cmath>#include<set>#include<algorithm>using namespace std;#define N 1007int n,m,p,ans;int a[N];int st[N];set<int> g;set<int>::iterator it;void dfs(int t){    int i;    if(t>=p){        int j,cnt;        for(i=t;i<m;++i){            cnt=0;            for(j=a[i];j<=n;j+=a[i]){                cnt+=1-(st[j]<<1);            }            if(cnt>0){                for(j=a[i];j<=n;j+=a[i]) st[j]^=1;            }        }        cnt=0;        for(j=1;j<=n;++j) cnt+=st[j];        ans=max(ans,cnt);        return ;    }    // yes    for(i=a[t];i<=n;i+=a[t]) st[i]^=1;    dfs(t+1);    for(i=a[t];i<=n;i+=a[t]) st[i]^=1;    // no    dfs(t+1);}int main(){    int T,tt=0;    int i,x;    scanf("%d",&T);    while(T--){        scanf("%d%d",&n,&m);        g.clear();        for(i=0;i<m;++i){            scanf("%d",&x);            g.insert(x);        }        p=g.size();        x=sqrt((double)n);        for(it=g.begin(),m=0;it!=g.end();++it,++m){            a[m]=*it;            if(a[m]>x) p=min(p,m);        }        ans=0;        memset(st,0,sizeof(st));        dfs(0);        printf("Case #%d: %d\n",++tt,ans);    }    return 0;}

H

I

/* * 状压DP * 题意: * 在一个N*M(N,M<=8)矩阵,最多放K(K<=N*M)个障碍 * 求不存在从(1,1)到(N,M)的矩阵个数(只能向下或向右移动)。 * 思路: * 因为最大就8*8,所以考虑状压DP。 * 用1表示存在从(1,1)到当前位置的路径,0则表示不存在。 * 假设前i-1行的状态pre,当前行i的状态now, * 那么就可以转移出前i行与(1,1)的联通状态。 * 因为最多只能放k个,所以用f[i][j][k]表示前i行, * 放置j个障碍后,与(1,1)联通状态为k的方案数。 * ans=∑f[n][1~k][st],其中st&2^(m-1)==0(即(n,m)与(1,1)不联通) * #include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 260#define MOD 1000000007int n,m,k;int s[N];int g[N][N];int f[10][70][N];int calc(int pre,int now){    int i,j,res=pre&now;    for(i=0;i<m;++i)    if((pre&(1<<i)) && (now&(1<<i))){        for(j=i+1;j< m && (now&(1<<j))>0;++j) res|=(1<<j);    }    return res;}void init(){    int i,j;    for(i=0;i<(1<<m);++i){        s[i]=0;        for(j=0;j<m;++j)        if(!(i&(1<<j))){            ++s[i];        }    }    for(i=0;i<(1<<m);++i)    for(j=0;j<(1<<m);++j){        g[i][j]=calc(i,j);    }}int main(){    int T,tt=0;    int i,j,pre,now,sta;    scanf("%d",&T);    while(T--){        scanf("%d%d%d",&n,&m,&k);        memset(f,0,sizeof(f));        init();        f[0][0][1]=1;        for(i=1;i<=n;++i){            for(now=0;now<(1<<m);++now)            for(j=s[now];j<=k;++j){                for(pre=0;pre<(1<<m);++pre){                    sta=g[pre][now];                    f[i][j][sta]+=f[i-1][j-s[now]][pre];                    if(f[i][j][sta]>=MOD) f[i][j][sta]-=MOD;                }            }        }        int ans=0;        for(i=1;i<=k;++i)        for(j=0;j<(1<<m);++j)        if(!(j&(1<<(m-1)))){            ans+=f[n][i][j];            if(ans>=MOD) ans-=MOD;        }        printf("Case #%d: %d\n",++tt,ans);    }    return 0;}

J

K

/* *    DP、取模 *    题意: *        从N*M(N,M<=10^6)的(1,1)向右和向下走到(N,M)的路径数,其中某些3*3的格子不能走(3*3的矩阵<=10),结果数对997取模。 *    思路: *        当没有障碍的时候,方案数为C(N+M-2,N-1)(即总共走N-1+M-1步,其中N-1步向下走) *        假设只有1个障碍,那么就要从原方案中去掉包含该障碍的方案数,记该障碍的坐标为(x,y), *        则不合法的方案为C(x-1+y-1,x-1)*C(N-x+M-y,N-x) *        令f[i]表示从(1,1)出发到(x[i],y[i])的合法路径数, *        则f[j]=C(x[j]-1+y[j]-1,x[j]-1)-∑C(x[j]-x[i]+y[j]-y[i],x[j]-x[i]),其中x[i]<=x[j]&&y[i]<=y[j] *        结果需要对997取模,对于组合数C(n,m)=n!/(m!(n-m)!),通过逆元和快速幂得到。 *        但是997可能比N、M小,导致比997大的阶乘均为0。 *        这样导致的问题是某些组合数不包含997,但是由于阶乘均为0,导致结果就为0了。 *        所以对于阶乘的预处理中要去掉997,另开数组记录997的指数。 *    问题: *        做的时候DP作法对了,但是没有意识到997导致的问题。 */#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 107#define M 2000007#define MOD 997int dx[]={-1,-1,-1,0,1,1,1,0};int dy[]={-1,0,1,1,1,0,-1,-1};int f[N];int P[M],G[M];struct Node{    int x,y;    bool operator<(const Node &p) const{        return x<p.x || (x==p.x && y<p.y);    }}    a[N];void init(){    int i,j;    P[0]=1;G[0]=0;    for(i=1;i<M;++i){        G[i]=G[i-1];        j=i;        while(j%MOD==0){            ++G[i];            j/=MOD;        }        P[i]=(j%MOD*P[i-1])%MOD;    }}int pow(int a,int b){    a%=MOD;    int res=1;    while(b>0){        if(b&1) res=(res*a)%MOD;        a=(a*a)%MOD;        b>>=1;    }    return res;}int work(int x,int y){    int res=P[x+y]*pow(P[x]*P[y],MOD-2)%MOD;    res=(res*pow(MOD,G[x+y]-G[x]-G[y]))%MOD;    return res;}void add(int &t,int x,int y){    a[t].x=x,a[t].y=y,++t;}int main(){    int T,tt=0;    int n,m,k,i,j,t,r,c;    init();    scanf("%d",&T);    while(T--){        scanf("%d%d%d",&n,&m,&k);        t=0;        add(t,1,1);        add(t,n,m);        for(i=0;i<k;++i){            scanf("%d%d",&r,&c);            for(j=0;j<8;++j){                add(t,r+dx[j],c+dy[j]);            }        }        sort(a,a+t);        f[0]=1;        for(i=1;i<t;++i){            f[i]=work(a[i].x-1,a[i].y-1);            for(j=1;j<i;++j)            if(a[j].x<=a[i].x && a[j].y<=a[i].y){                f[i]=(f[i]+MOD-f[j]*work(a[i].x-a[j].x,a[i].y-a[j].y)%MOD)%MOD;            }        }        printf("Case #%d: %d\n",++tt,f[t-1]);    }    return 0;}
0 0