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

题目来源:
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 << endl
const 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 1000000007
LL 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-6
typedef 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 1007
int 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 1000000007
int 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 997
int 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值