题目来源:
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;
}