0%

题意:题目非常长,前面讲了一大堆刚性的和灵活的,但是问的是网格图。很显然网格图不是刚性的(如下图),但是我们可以通过添加对角线使得它变为刚性的,问有多少种添加的方法使变为刚性的。
网格图
题解:可以~~(很难)~~发现,在摆动过程中,原来处于同一行的竖边一定平行;同理,原来处于同一列的横边一定平行。我们要做的即为所有横边垂直于所有竖边,那么一行和一列可以分别看做整体。更进一步地,如果两行竖边都垂直于同一列横边,这两行就属于同一个集合。
这里使用一个经典模型,将网格图的行和列作为二分图的左右两个点集,(x,y)连边就表示x行的竖边垂直于y列的横边,那么问题可以转化为最终n+m个点都连通的方案数。但是要注意,连边和添加对角线并不完全等价!添加对角线包含了主对角线和次对角线,而普通的连边没有这种含义。

我们设计一种动态规划(或者说递推),那它必然是总方案数减去不可行的方案数:
$$dp[n][m]=3^{nm}-\sum_{i=1,j=0}^{i<n||j<m} C_{n-1}^{i-1}C_{m}^{j}\cdot dp[i][j]\cdot 3^{(n-i)(m-j)}$$
$3^{nm}$表示对于每一个格子我们总是有无对角线,主对角线,次对角线三种选择。后面的减数项中,为了保证不重复,我们总是枚举左边明确的某一点(例如$1$号点)所在的连通块的左右大小。具体地,我们从$2-n$号点中选出$i-1$个,从$m$个点中选出$j$个,它们内部会有$dp[i][j]$种方案,至于剩下的$n-i$个点和$m-j$个点的关系,任意选择即可。

理解了之后写代码非常简单,可以用预处理做到$O(n^4)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=11;
const int MO=1e9+7;
ll C[N][N],dp[N][N],po3[N*N];

void init()
{
po3[0]=1;
for (int i=1;i<N*N;i++) {
po3[i]=po3[i-1]*3%MO;
}
C[0][0]=1;
for (int i=1;i<N;i++) {
C[i][0]=C[i][i]=1;
for (int j=1;j<i;j++) {
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MO;
}
}
for (int n=1;n<N;n++) {
for (int m=0;m<N;m++) {
dp[n][m]=po3[n*m];
for (int i=1;i<=n;i++) {
for (int j=0;j<=m;j++) {
if (i==n&&j==m) continue;
dp[n][m]-=C[n-1][i-1]*C[m][j]%MO*dp[i][j]%MO*po3[(n-i)*(m-j)]%MO;
dp[n][m]=(dp[n][m]%MO+MO)%MO;
}
}
}
}
}

int main()
{
init();
int n,m;
while (scanf("%d%d",&n,&m)!=EOF) {
printf("%lld\n",dp[n][m]);
}
return 0;
}
阅读全文 »

Tips

仅含题解,代码详见github喜欢的话点个star
这几天又把China-Final的题补了一些,比较蛋疼的是为啥根本搜不到题解。。。我觉得我要多加些关键字,2016 ICPC China-Final,codeforces gym101194。题目在cf可以交,因为只有PDF就不每一题都加链接了。

A. Number Theory Problem

$7$的二进制表示为$111$,所以$111$, $1110$($111$移位的结果)都能被$7$整除。而$2^k-1$的二进制表示为$\underbrace{11\dots1}_{k\text{ times}}$。所以只需要$k$被$3$整除即可。

C. Mr. Panda and Strips

这题据说比赛的时候$O(n^4)$加优化就能过。。。反正我们也没试。。。
我最开始看到的是出题人的题解,消化了很久并且按照他讲的写了,结果WA了以后仔细改了改又TLE了。然后再回去看他的题解,感觉错误不止一个啊。。。= =
我出于无奈只好直接问q巨要了一份代码。。。
C_Q.cpp
这个代码的意思大概就是先预处理每个颜色分布在哪几个位置,每个位置最多能延伸的区间。然后枚举第一个区间的右边界$i$,再枚举左边界$j$,$j$每减小1,就用新加入的颜色$a[j]$在之后出现的位置$x$(即$a[x]==a[j], x>j$)去截保存在set里的一些区间(所有满足 $l\leq x \leq r$ 的区间)。对于每一个$x$,在全截完了以后再插入在$x$左边的最长的$[ml,x-1]$,在$x$右边的最长的$[x+1,mr]$。代码中multiset<int> len是辅助的保存长度的有序列。
这个算法对于每一个$i$都是先插入$O(n)$个区间,对于每个区间又会分割成$O(n)$个区间,显然复杂度是$O(n^3logn)$。时间达到了3.5s,常数比较大,操作的区间总个数更接近于$4n^2$。
后来又看到一份代码(是cf上的vj号交的,所以也不知道是哪位大侠)感觉很不错,跟这个其实是一个思路的,于是就学习了一下。
预处理时采用了时间戳的int数组,也可以像上面那样每次清空bool数组。
C.cpp
这个方法更优的地方在于它并没有一定要保存合法的区间(如$[1,n]$通常不合法),它保存的是相对于第一个区间合法的区间,以及该区间内自我合法的最大长度
复杂度同样为$O(n^3logn)$,但是实际操作区间接近$2n^2$,所以运行时间也大概是一半。

D. Ice Cream Tower

这题应该说比较容易。读懂题目应该可以想到答案具有单调性,所以可以二分答案。记二分当前结果为$m$,那么问题转化为判定能不能堆出$m$个塔。这个也比较容易,我们只要将冰淇淋按从小到大排序,一层一层贪心地堆就可以了,因为这个冰淇淋如果当前不能用上,那么后续更不可能用上。(从大到小堆我没有测试,据说也可以通过)
不想动态申请内存的可以像这样使用滚动数组。

E. Bet

。。。这题本身是容易的。。。但是。。。
记对第$i$个赌局下注$c_i$,则题意就是对于答案集合$S$中的每个$i$都有$$c_i+c_i*\cfrac{b_i}{a_i}>\sum^{i\in S}_{i}c_i\quad=>\quad \cfrac{c_i}{\sum^{i\in S}_{i}c_i}>\cfrac{a_i}{a_i+b_i}$$该式对$i$求和可得$\sum^{i\in S}_{i}\cfrac{a_i}{a_i+b_i}<1$。接下来排个序贪个心就不用说了吧。。。然而boss却是精度问题。。。多少队伍交了10+发就是因为精度问题。。。反正听说有1-(1e-50)的= =
于是我又是问q巨要的代码。。。然后我感觉Java很厉害。。。

阅读全文 »

Tips

仅含题解,代码详见github喜欢的话点个star
前几天终于把合肥的题补了一些。。。再往下就是lct了,暂时不学。

A、传递

题解:将P图和Q图的边取并生成一个新图(下称“合成”),有环则原来是不传递的。再将P图和Q的反图合成,有环则原来不传递的。
感觉知道结论还是不难理解的。。。反正我根本想不到。。。结束的时候听到有用最短路做的,这个才是重大失误,我们都没想到。
代码是按题解写的。

C、朋友

分析:不与根相连的值为1的边操作两次可以变为0,与根相连的值为1的边操作一次可以变为0。推理可知,答案仅和与根直接相连的那些1有关。

D、平行四边形

全场代码最短的题。。。我一直以为它代码是个几何题,好像读题的时候直接跳过了。。。现在发现只要敢做就能做出来= = 又是一个重大失误。
这题主要是一个数学推导的题目。。。
取两点 $v_1,v_2\in S,v_1\neq v_2$,可以利用平行四边形性质解得直线上的两点 $v_3,v_4$ 。
$$\begin{cases}
x_1+x_2=x_3+x_4 \\
y_1+y_2=y_3+y_4 \\
ax_3+by_3=0 \\
a’x_4+b’y_4=0
\end{cases}$$解得:$$\begin{cases}
x_3=\cfrac{a′bx_1+a′bx_2+bb′y_1+bb′y_2}{a′b-ab’} \\
y_3=\cfrac{aa′x_1+aa′x_2+ab′y_1+ab′y_2}{ab′-a′b}
\end{cases}$$再利用叉积计算面积:$$S=2\times \frac{1}{2}\times |\overrightarrow{v_1v_2}×\overrightarrow{v_1v_3}|=|(x_2-x_1)(y_3-y_1)-(y_2-y_1)(x_3-x_1)| \\
=|(ax_2+by_2)(a′x_2+b′y_2)-(ax_1+by_1)(a′x_1+b′y_1)|\triangleq |f(v_2)-f(v_1)|$$

E、扫雷

相邻两格的差等于外面两列的差,直接递推。要特判 $n=1$ 的情况。

阅读全文 »

Gorgeous Sequence

读完题,可以知道显然要维护最大值和区间和,可以用线段树。写着写着感觉第一种操作好像不能延迟更新,然后就不会了。。。
查题解的时候发现都是线段树,不过各有各的做法,后来有一种说是吉如一论文中介绍的,可以证明每次操作均摊复杂度为 $O(logN)$ 。这个论文的标题我倒是找到了(吉如一 -《区间最值操作与历史最值问题》)然而论文没找到。。。
做法如下:线段树的每个节点记录最大值a,次大值b,区间和sum,以及最大值个数cnt。当进行第一种操作时,

  1. 如果当前节点有 $a \leq t$ ,则直接退出。这个很好理解,即区间内所有数都不变。
  2. 如果当前节点有 $b \lt t$,则先利用 $a$ 和 $cnt$ 更新$sum$ 再更新 $a$ 后退出。这里要注意的是绝对不能加等号!若 $b=t$ 也是如此退出,则之后的次大值和最大值个数都不准确【没错我就是因为这个WA了几次 ←_←
  3. 直接往下递归

第二种第三种为线段树常规的操作,不表。
期间还发生一件事,找了一份3k+的代码边抄边理解然后调了半天终于A了,结果又找到一份代码,感觉非常好懂而且只有2k+,于是又照着这个写了一遍。。。可见理思路是多么的重要。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define ls rt<<1
#define rs rt<<1|1

const int maxn = 1000000;
struct Seg {
ll sum;
int a,b,cnt;
void spfy(int t) {
if (a<=t) return;
sum-=(ll)(a-t)*cnt;
a=t;
}
} s[maxn<<2];

void push_up(int rt)
{
s[rt].sum=s[ls].sum+s[rs].sum;
s[rt].a=max(s[ls].a,s[rs].a);
s[rt].b=max(s[ls].b,s[rs].b);
s[rt].cnt=0;
if (s[ls].a==s[rt].a) s[rt].cnt+=s[ls].cnt;
else s[rt].b=max(s[rt].b,s[ls].a);
if (s[rs].a==s[rt].a) s[rt].cnt+=s[rs].cnt;
else s[rt].b=max(s[rt].b,s[rs].a);
}

void push_down(int rt)
{
s[ls].spfy(s[rt].a);
s[rs].spfy(s[rt].a);
}

void build(int l,int r,int rt)
{
if (l==r) {
scanf("%d",&s[rt].a);
s[rt].sum=s[rt].a;
s[rt].b=-1;
s[rt].cnt=1;
return;
}
int m=(l+r)>>1;
build(lson);
build(rson);
push_up(rt);
}

void update(int L,int R,int t,int l,int r,int rt)
{
if (s[rt].a<=t) return;
if (L<=l&&r<=R&&s[rt].b<t) {/* Do not add '=' !!!
if s[rt].b == t,
it supposed to be push_down,
because s[rt].b, s[rt].cnt cannot be determined. */
s[rt].spfy(t);
return;
}
push_down(rt);
int m=(l+r)>>1;
if (L<=m&&s[ls].a>t) update(L,R,t,lson);
if (R>m&&s[rs].a>t) update(L,R,t,rson);
push_up(rt);
}

int getmax(int L,int R,int l,int r,int rt)
{
if (L<=l&&r<=R) return s[rt].a;
int m=(l+r)>>1;
int res=0;
push_down(rt);
if (L<=m) res=max(res,getmax(L,R,lson));
if (m<R) res=max(res,getmax(L,R,rson));
return res;
}

ll getsum(int L,int R,int l,int r,int rt)
{
if (L<=l&&r<=R) return s[rt].sum;
int m=(l+r)>>1;
ll res=0;
push_down(rt);
if (L<=m) res+=getsum(L,R,lson);
if (m<R) res+=getsum(L,R,rson);
return res;
}

int main()
{
int T,n,m;
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
build(1,n,1);
while (m--) {
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if (op==0) {
int t;
scanf("%d",&t);
update(x,y,t,1,n,1);
} else if (op==1) {
printf("%d\n",getmax(x,y,1,n,1));
} else {
printf("%lld\n",getsum(x,y,1,n,1));
}
}
}
return 0;
}
阅读全文 »

某文,很6 。
昨天在某群看到一个同级同专业的同学解答了折半查找的问题,感觉代码写得挺不错的,而且跟别人争辩的时候也不慌不忙。再一看截图,这环境好像是iPad+vim。。。瞬间吓尿,又想起来他这几次比赛好像都很不错,于是赶紧加了好友。
一加好友他竟然说我银牌大佬,还告诉我是AA说的AA真是看得起我😂。后来又了解到他考过pat乙级和pat甲级,甲级80+,凭我感觉应该还是挺屌的。再后来就问问他日常什么系统啊之类的。。。结果发现他每天晚上的日常是躺在床上拿iPad连接自己桌上的笔记本然后…用vim写代码
Cool!妈的感觉这才是大学生啊!当然,首先我需要一个iPad 。
又问了问有没有搞过Web方面的,他说用python爬过10个G的美女图片。
Cool!妈的感觉这才是大学生啊!当然,首先我需要学会python 。
于是我说我想学python ,他就说我这儿有几本书你要不要看啊 。商量了一下,我就直接去他寝室了。
见面当然还是要聊啊,中学没搞过信息学竞赛,但是自学过C语言,大学还玩过单片机,然后转专业到计算机,现在在看算法导论,还在跟别人一起搞3D玩。相比之下我就很挫了,初中用Pascal搞信息学竞赛,高中用C++搞信息学竞赛,python只会helloworld,从来没看过什么正经书…
轻松转专业,估计还是个学霸。我觉得他就是我想要的那种状态,但是也很难说清楚。不过我现在有事干了:

  • 看看这本python核心编程
  • 学会最基本的vim用法要不然怎么搞服务器啊
  • 程序竞赛全面使用c++11,多吸取优雅写法【我觉得一定会很爽
阅读全文 »

Rotation

题意:$N×M$ 的网格图,给定一条网格线连成的闭合路径,计算所有格子转动值的平方和。假设一辆车沿着路径移动,一个人站在某个格子正中间始终对着车,这个人在车开始到停下顺时针转动了 $x$ 度,则他的转动值为 $\frac{x}{360}$ (可以为负数)。
题解:首先,因为起点和终点相同,每个格子的转动值一定为整数。
其次,若路径为简单环,环内的格子转动值均为 $\pm 1$,环外的格子转动值均为 $0$ 。

环外某格的转动值一定为0
接着可以发现,每次路径向下时,为左边的格子贡献正的转动值,为右边的格子贡献负的转动值。
用一个看来的巧妙的做法:每次只考虑路径对右侧的格子的影响。上行一次,右侧所有格子加一,下行一次,右侧所有格子减一。这时,这个做法已经不仅限于简单环了。
现在问题转化为给一个矩阵增量,由于只需要最后结果,我们可以利用差分的思想。
在一维时,差分可以将区间增量变成两个点的增量。差分即为前缀和的逆运算,回忆一下二维前缀和的递推:$$sum[i][j]=a[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]$$

设计如下的增量:

二维的差分

进行二维前缀和递推以后即可得到真实的数。

考虑到本题每次更新的矩阵都是直到右边界,所以可以忽略右边的两个增量。

最后,本题需要生成一个长宽不定的矩阵,C++使用 vector 比较方便,C语言查了一下指针和malloc大概要十多行吧。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
int T,n,m,k;
vector< vector<int> > mp;

void update(int r1,int r2,int c,int val)
{
mp[r1][c]+=val;
mp[r2][c]-=val;
}

int main()
{
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d%d%d",&n,&m,&k);
mp=vector< vector<int> >(n+2,vector<int>(m+2,0));
int r=1,c=1;
while (k--) {
char d[2];
int s;
scanf("%s%d",d,&s);
if (d[0]=='U') {
update(r-s,r,c,1);
r-=s;
continue;
}
if (d[0]=='D') {
update(r,r+s,c,-1);
r+=s;
continue;
}
if (d[0]=='L') {
c-=s;
} else {
c+=s;
}
}
ll ans=0;
for (int i=1;i<=n;i++) {
for (int j=1;j<=m;j++) {
mp[i][j]=mp[i][j]+mp[i-1][j]+mp[i][j-1]-mp[i-1][j-1];
ans+=(ll)mp[i][j]*mp[i][j];
}
}
printf("Case #%d: %lld\n",t,ans);
}
return 0;
}

其实我们可以发现,不管考虑哪一侧都可以解决这个问题,当然因为最开始在左上角,维护左侧和上侧可能比较困难。下面放一个维护下侧的:

阅读全文 »

B-捧杯

受力分析。。。贴这题主要是因为看到了向量化简有一个很简洁的作法~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

int gcd(int a,int b)
{
if (a==0||b==0) {
return max(a+b,1);//cool~
}
while (b) {
int t=b;
b=a%b;
a=t;
}
return a;
}

void spfy(int &a,int &b)
{
int g=gcd(abs(a),abs(b));
a/=g;b/=g;
}

int main()
{
int T;
scanf("%d",&T);
while (T--) {
int x0,y0,n;
int ansx=0,ansy=0;
scanf("%d%d",&x0,&y0);
scanf("%d",&n);
for (int i=1;i<=n;i++) {
int x,y;
scanf("%d%d",&x,&y);
ansx+=x-x0;
ansy+=y-y0;
}
spfy(ansx,ansy);
printf("%d %d\n",ansx,ansy);
}
return 0;
}

C-汪老司机

交错dp。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N=10010;
const ll INF=1LL<<60;
ll a[N],b[N];
ll f[N][20],g[N][20];

int main()
{
int T,n,k;
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
}
for (int i=1;i<=n;i++) {
scanf("%lld",&b[i]);
}
for (int i=1;i<=n;i++) {
f[i][0]=f[i-1][0]+a[i];
g[i][0]=g[i-1][0]+b[i];
}
for (int j=1;j<=k;j++) {
for (int i=1;i<=n;i++) {
f[i][j]=min(f[i-1][j]+a[i],g[i-1][j-1]+a[i]);
g[i][j]=min(g[i-1][j]+b[i],f[i-1][j-1]+b[i]);
}
}
printf("%lld\n",min(f[n][k],g[n][k]));
}
return 0;
}

D-蒟蒻的任务分配

这题是比赛的时候唯一没什么想法的。先贴一下通过某种py搞到的出题人的题解:

首先考虑到,没有可容性的事件一定是不能同时进行的,也就是说,最短也要花费这些事件长度的总和,如果这些事件长度的总和是大于有可容性的事件的长度的总和,那么最少耗费时间就是没有可容性事件的长度总和

首先因为最多同时开两件事,那么自然尽量把所有事分为尽量相等的两段,如果能够安排出合理的方案,那么该解一定是最优的。

接下来证明在这种条件下若分成尽量相等的两段,总能够安排出合理的序列使得任务能够正常进行

假设分为两段第一段里没有可容性的事件总长为x1,有可容性的事件的总长为y1。第二段里没有可容性的事件总长为x2,有可容性的事件的总长为y2。那么有x1+x2<=y1+y2不妨设x1+y1<=x2+y2那么有如下安排方案先做第一段里没有可容性的事件,同时开始做第二段里有可容性的事件如果y2 < x1,那么根据x1+y1<=x2+y2,一定有x2>y1,那么有x1+x2>y1+y2,与该情况相悖,所以y2>=x1。那么在第一段没有可容性的事件全部做完后,开始做第一段有可容性的事件然后在第二段有可容性的事件全部做完后,开始做第二段没有可容性的事件随后任务肯定能够没有冲突的顺利进行,证毕。

接下来只需要把所有任务分为时间尽量相等的两半就可以了,这一部分可以背包暴力跑01背包时间复杂度是 $O(t*n^2*costi)$ 但是考虑到costi很小,可以记录costi相等的事件的数量,然后做二进制优化优化后的时间复杂度为 $O(t*cost^2*nlogn)$

本题放过了暴力01背包的解法

不得不说这个想法很巧妙,反正我肯定是想不到的。
虽然出题人说01背包能过但是我并没有过。。。于是我还是写了多重背包。。。结果因为没清f[] WA了好几次。。。

阅读全文 »

J. Find Small A

一眼题没看清以为移4位WA了一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;

int main()
{
int n;
while (scanf("%d",&n)!=EOF) {
int ans=0;
for (int i=1;i<=n;i++) {
int x;
scanf("%d",&x);
while (x) {
ans+=((x&255)=='a');
x>>=8;
}
}
printf("%d\n",ans);
}
return 0;
}

H. To begin or not to begin

简单计算或者由生活常识:每次抽中红球的概率都是相等的,所以只需要判断先后手抽的次数,也就是判断奇偶性。这里题目没说k是什么,但是看样例应该差不多反正先随便交一发【据说当时pc^2上还给了错误的解释。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;

int main()
{
int n;
while (scanf("%d",&n)!=EOF) {
printf("%d\n",(n+1)&1);
}
return 0;
}

I. Convex

正弦面积公式,注意角度弧度转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <algorithm>
using namespace std;

const double PI=acos(-1.0);

int main()
{
int n,r;
while (scanf("%d%d",&n,&r)!=EOF) {
long double ans=0;
for (int i=1;i<=n;i++) {
int a;
scanf("%d",&a);
ans+=sin(a/180.0*PI)*r*r;
}
double outter=ans/2.0;
printf("%.3f\n",outter);
}
return 0;
}

D. A Simple Math Problem

阅读全文 »

Battle ships

做的时候能不能搜索想了半天。。。后来查了一下竟然是二分图经典模型。。。所以觉得应该记下来。。。

题目大意:
在海上(*)放战舰,任意两个战舰不能出现在同一行或列,除非中间有冰山(#)相隔。问最多放多少战舰。

解题思路:
现将行和列,以冰山(#)为分隔,分割成多段。对于每一个可能放置的位置(*),将其所在的行和列的分段相连。表达的意思就是,这个点如果放置,那么相邻的同一区段(*、o)都不能再用。对按行分割按列分割这两个点集做一次二分图匹配即可。

参考资料

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N=2505;
struct Edge {
int go,next;
} eg[N<<1];
char mp[55][55];
int mx[55][55];
bool vis[N];
int res[N],last[N];
int tot;

void adde(int x,int y)
{
eg[++tot].go=y;
eg[tot].next=last[x];
last[x]=tot;
}

bool dfs(int u)
{
for (int i=last[u];i;i=eg[i].next) {
int &v=eg[i].go;
if (!vis[v]) {
vis[v]=1;
if (!res[v]||dfs(res[v])) {
res[v]=u;
return true;
}
}
}
return false;
}

int main()
{
int T,n,m;
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
int nx=0,ny=0;tot=0;
//nx为按行分割的点数,ny为按列分割的点数,tot为邻接表边数.
memset(last,0,sizeof(last));
for (int i=0;i<n;i++) {
scanf("%s",mp[i]);
int f=0;
for (int j=0;j<m;j++) {
if (mp[i][j]=='*') {
if (!f) {
mx[i][j]=++nx;
f=1;
} else {
mx[i][j]=nx;
}
} else if (mp[i][j]=='#'&&f==1) {
f=0;
}
}
}
for (int j=0;j<m;j++) {
int f=0;
for (int i=0;i<n;i++) {
if (mp[i][j]=='*') {
if (!f) {
adde(mx[i][j],++ny);
f=1;
} else {
adde(mx[i][j],ny);
}
} else if (mp[i][j]=='#'&&f==1) {
f=0;
}
}
}
memset(res,0,sizeof(res));
int ans=0;
for (int i=1;i<=nx;i++) {
memset(vis,0,sizeof(vis));
if (dfs(i)) ans++;
}
printf("%d\n",ans);
}
return 0;
}
阅读全文 »

Necklace

题意:要用n个阳石和n个阴石来串一个项链(环),规定阳石旁边只能是阴石,阴石旁边只能是阳石,现在有 m 对影响关系,x 号阳石与 y 号阴石相邻会使阳石变暗(照样可以用),问串这个项链最少让几个阳石变暗

题解:一看就是两个点集之间连边,瞬间想到二分图。但是后面其实并不好想。。。

首先发现 n 很小,即使是全排列的复杂度也能承受,那么我们可以枚举阴石的每一种排列,并且把阳石编号阳石摆放位置作为两个点集匹配。用 $a[i]$ 表示第 $i$ 个阴石的编号,用 $g[i][j]$ 表示 $i$ 号阳石能否放在第 $j$ 个位置,这时就要考虑第 $i$ 个阴石和第 $i+1$ 个阴石对第 $i$ 个阳石是否有影响了,把没影响的连边,做一次二分图最大匹配,用 n-最大匹配 更新答案即可。
复杂度为 $O(N!\times N^{2})$ ,很容易TLE。。。
于是去查了查,发现有1个算法优化和2个常数优化。。。

  1. 在环的意义下,其实只有 $(n-1)!$ 种排列,比如:
    1 2 3 ... n
    n 1 2 ... n-1
    n-1 n 1 ... n-2
    这几种排列的答案一定是相等的
  2. ans=0 时已经找到最优解,可以直接退出
  3. 使用 next_permutation() 会比递归形式更快。

加了这些优化以后跑得飞快,只要400+ms。。。代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;

const int N=12;
int a[N],b[N],link[N];
int g[N][N],mp[N][N];
bool used[N];
int n,m,ans;

bool dfs(int u)
{
for (int v=1;v<=n;v++)
if (g[u][v]&&!used[v]) {
used[v]=true;
if (link[v]==-1||dfs(link[v])) {
link[v]=u;
return true;
}
}
return false;
}

int hungary()
{
int res=0;
memset(link,-1,sizeof(link));
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) {
g[i][j]=0;
if (j==n) {
if (!mp[i][a[j]]&&!mp[i][a[1]]) g[i][j]=1;
} else {
if (!mp[i][a[j]]&&!mp[i][a[j+1]]) g[i][j]=1;
}
}
for (int i=1;i<=n;i++) {
memset(used,0,sizeof(used));
if (dfs(i)) res++;
}
return res;
}

int main()
{
while (scanf("%d%d",&n,&m)!=EOF) {
if (!n||!m) {
puts("0");
continue;
}
for (int i=1;i<=n;i++) a[i]=i;
memset(mp,0,sizeof(mp));
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
mp[x][y]=1;
}
ans=n;
do {
ans=min(ans,n-hungary());
if (!ans) break;
} while (next_permutation(a+2,a+n+1));
printf("%d\n",ans);
}
return 0;
}
阅读全文 »