线段相交
1127
题意
判断两条线段是否相交,对于N条线段,间接相交也算相交。对于每次询问,判
断给定的两条线段是否相交
题解
这个题目分成两部分,一部分是基础的判断两条线段是否相交,用一个bool数组来存储信息。另一部分是判断间接相交,可以用floyd-warshall(比较巧妙)或者并查集.第一部分就是套模板。
点可以用结构体存储(推荐),线段也可以用结构体存储或pair
判断两条线段相交有多重模板,比如判是否平行、重合、求两条直线交点,判断交点是否在线段上,还有ccw(counter clock wise)函数,可参考discuss
比较常见的是快速排斥和跨立检验
以线段为对角线,作平行于x轴、y轴的射线,使之形成矩形,若两个矩形没有相交,则线段不相交(可以排除大部分)
不满足快速排斥进入跨立检验,判断两个点是否在线段的两侧(即跨立),判断方法是外积的符号是否相反,等于0说明在线上
如果两两互相跨立,则线段相交
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
|
#include<iostream>
#include<algorithm>
using namespace std;
int n;
bool con[15][15];
int p,q;
struct point
{
double x,y;
//point(double a,double b) : x(a),y(b) {} //!!!!!会发生编译错误
};
pair<point,point> seg[15];
double dir(point a,point b,point c) //用外积
{
return (c.x-a.x)*(c.y-b.y) - (c.y-a.y)*(c.x-b.x);
}
bool judge(pair<point,point> p1,pair<point,point> p2)
{
point a,b,c,d;
a=p1.first;
b=p1.second;
c=p2.first;
d=p2.second;
//快速排斥
if(min(a.x,b.x)>max(c.x,d.x) or min(c.x,d.x)>max(a.x,b.x) or
min(a.y,b.y)>max(c.y,d.y) or min(c.y,d.y)>max(a.y,b.y))
return false;
//跨立检验 (int 可改成double)
else
{
int d1,d2,d3,d4;
d1=dir(a,b,c);
d2=dir(a,b,d);
d3=dir(c,d,a);
d4=dir(c,d,b);
return d1*d2<=0 and d3*d4<=0; //!!!
}
}
int main()
{
while(scanf("%d",&n)==1 and n!=0)
{
for(int i=1;i<=n;i++) con[i][i]=true;
for(int i=1;i<=n;i++)
scanf("%lf%lf%lf%lf",&seg[i].first.x,&seg[i].first.y,&seg[i].second.x,&seg[i].second.y);
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
{
if(judge(seg[i],seg[j])) con[i][j]=con[j][i]=true;
else con[i][j]=con[j][i]=false;
}
for(int k=1;k<=n;k++) //Floyd-Warshall算法或并查集都可以
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
con[i][j] |= con[i][k] and con[k][j];
while(scanf("%d%d",&p,&q)==2 and p!=0)
puts(con[p][q] ? "CONNECTED" : "NOT CONNECTED");
}
return 0;
}
|
艰难的debug
计算几何有些很麻烦,代码太相似,debug比较难,有时还要考虑精度问题(即误差eps)
debug一个上午
写完代码CE,seg[i].first.x错误,把stl_pair.h中的代码小改一下可以通过,但放到oj上肯定不行,怕污染代码还是不这样做。最后发现把构造函数去掉可以通过
自测,最后一个样例没过,发现是边界情况,把跨立检验最后一步的等号加上
自测,中间有一个样例没过,作图,把样例分离出来,过了。说明可能不是judge函数的问题。
单测样例,没过,说明错误不是受别组影响
编译器debug,把函数内的局部变量,变成全局变量,add watch
发现d1 d2 d3 d4都等于0,a.x a.y b.x b.y ….有问题
继续add watch
发现seg[1].first的内容就有问题,是double边界数,然后发现最终问题 %d 赋值给了double型变量
总结
提高debug能力,少犯白痴错误。
写代码要有清晰性和完整性,这样鲁棒性更强。
===========================================================================================================
并查集
1182
题解
并查集
看到信息的内容,有并查集的影子
依次遍历信息,对于每一条信息,因为没有说种类,所以设置三个种类,把每一种情况都加上去,比如:x和y属于同一类,则合并x和y属于A类,B类,C类
判断是否是错误信息,只要判断是否与前面信息矛盾即可
并查集的时间复杂度O(α(n)) α(n)是阿克曼函数的反函数,比O(lgn)还快
代码如下(并查集的模板 + 并查集的应用)
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
|
#include<iostream>
using namespace std;
int T[100005],X[100005],Y[100005];
int n,k;
//union find
int par[150005]; //父亲
int rankk[150005]; //树的高度,!!!元素是根 (优化用)//reference to "rank" is ambiguous
//初始化,要用并查集前要初始化
void init(int n)
{
for(int i=0;i<n;i++)
{
par[i]=i;
rankk[i]=0;
}
}
//查询树的根
int find(int x)
{
if(par[x]==x) return x;
else return par[x]=find(par[x]); //return find(par[x]);也可以 。par[x]=find(par[x]) 是路径压缩优化
}
//合并x,y //优化高度(如果rankk不同,那么从rankk小的向大的连边
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return ; //判断是否已在同一个集合内
if(rankk[x]<rankk[y]) par[x]=y; //!!!利用它们的 根 进行合并
else
{
par[y]=x;
if(rankk[x]==rankk[y]) rankk[x]++; //这时候rankk还是原来的高度
}
}
//判断是否在同一个集合内
bool same(int x,int y)
{
return find(x)==find(y);
}
int main()
{
cin>>n>>k;
for(int i=0;i<k;i++)
scanf("%d%d%d",&T[i],&X[i],&Y[i]);
init(n*3);
int ans=0;
for(int i=0;i<k;i++)
{
int t=T[i],x=X[i]-1,y=Y[i]-1;
//错误的编号
if(x<0 || x>=n || y<0 || y>=n)
{
ans++;
continue;
}
else if(t==1) //第一种类型
{
if(same(x,y+n) || same(x,y+2*n)) ans++; //判断是否矛盾 //!!!只需要判断x在A类就行,因为每次unite都
//涵盖所有情况,它们是平行影响的,判断一个就相当于判断所有
else
{
unite(x,y);
unite(x+n,y+n);
unite(x+n+n,y+n+n);
}
}
else if(t==2)
{
if(same(x,y) || same(x,y+2*n)) ans++;
else
{
unite(x,y+n);
unite(x+n,y+2*n);
unite(x+2*n,y);
}
}
}
cout<<ans<<endl;
return 0;
}
|
===========================================================================================================
完全背包问题
1384
题解
题意:完全背包问题求最小价值
时间复杂度O(nm)
二维数组如下,MLE
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<iostream>
#include<algorithm>
using namespace std;
int dp[505][10005];
int v[505];
int w[505];
int t;
int e,f;
int n,m;
const int inf=0x3f3f3f3f;
int main()
{
cin>>t;
while(t--)
{
cin>>e>>f;
m=f-e;
cin>>n;
//init
fill(dp[0],dp[0]+n*(m+1),inf);
dp[0][0]=0;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=min(dp[i-1][j],dp[i][j-w[i]]+v[i]);
if(dp[n][m]!=inf)
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[n][m]);
else
printf("This is impossible.\n");
}
return 0;
}
|
改一维数组,AC
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
|
#include<iostream>
#include<algorithm>
using namespace std;
int dp[10005];
int v[505];
int w[505];
int t;
int e,f;
int n,m;
const int inf=0x3f3f3f3f;
int main()
{
cin>>t;
while(t--)
{
cin>>e>>f;
m=f-e;
cin>>n;
//init
fill(dp,dp+m+1,inf); //!!!!!
dp[0]=0;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)
dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
if(dp[m]!=inf)
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[m]);
else
printf("This is impossible.\n");
}
return 0;
}
|
这个地方要注意 fill 到 dp+m+1
滚动数组好啊
AC代码
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
|
#include<iostream>
#include<algorithm>
using namespace std;
int dp[2][10005];
int v[505];
int w[505];
int t;
int e,f;
int n,m;
const int inf=0x3f3f3f3f;
int main()
{
//freopen("input.txt","r",stdin);
cin>>t;
while(t--)
{
cin>>e>>f;
m=f-e;
cin>>n;
//init
fill(dp[0],dp[0]+2*(m+1),inf);
dp[0][0]=0;
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
if(j<w[i])
dp[i&1][j]=dp[(i-1)&1][j];
else
dp[i&1][j]=min(dp[(i-1)&1][j],dp[i&1][j-w[i]]+v[i]);
if(dp[n&1][m]!=inf)
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[n&1][m]);
else
printf("This is impossible.\n");
}
return 0;
}
|
总结
这种类型的dp有两个重点,初始化边界条件,找递推式
求最小,一般初始化为inf,dp[0][0]=0;
求最大,一般初始化为0
必要时可用滚动数组
完全背包递推式的证明(这里证明求最大值的情况)
dp[i][j]=max{dp[i-1][j-k*w[i]]+k*v[i] | k>=0}
=max(dp[i-1][j] , max{dp[i-1][j-k*w[i]]+k*v[i] | k>=1}
=max(dp[i-1][j] , max{dp[i-1][(j-w[i])-k*w[i]]+k*v[i] | k>=0}+v[i])
=max(dp[i-1][j] , dp[i][j-w[i]]+v[i])
这种分离思想很常见
===========================================================================================================
线性dp LCS
1458
题解
水一题线性dp裸题–LCS(longest common subsequence)
因为没加string头文件CE了3次(被error误导了)
ac代码
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
|
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
string s1,s2;
int dp[1000][1000]; //dp[i][j] 表示s1到i,s2到j的lcs长度
int len1,len2;
int lcs(string s1,string s2)
{
len1=s1.length();
len2=s2.length();
memset(dp,0,sizeof(dp));
for(int i=0;i<len1;i++)
for(int j=0;j<len2;j++)
if(s1[i]==s2[j])
dp[i+1][j+1]=dp[i][j]+1;
else
dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
return dp[len1][len2];
}
int main()
{
//freopen("input.txt","r",stdin);
while(cin>>s1>>s2)
cout<<lcs(s1,s2)<<endl;
return 0;
}
|
===========================================================================================================
凸包
2187
题意
平面上有n个不重合的点,求两个点的最远距离
题解
由于点的个数为50000,所以暴力超时
构造凸包,遍历凸包上的点即可
坐标范围在n内的凸多边形(顶点在格点上)的顶点个数最多为O(√n)(尝试不严谨的画图证明,和公差为1的等差数列求和有关,所以是平方关系)
所以构造凸包后,暴力遍历的时间复杂度为O(n)
构造凸包可以用模板
此处介绍的是时间复杂度O(nlgn)的graham扫描法
外积是很常用的工具,此处利用外积的坐标公式的符号判断凹凸性
可以对点先排序,然后按逆时针方向依次遍历点,先构造凸包的下侧,到达最右端时,构造凸包的上侧
代码如下
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
|
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
struct point
{
double x,y;
}ps[50005];
double s_dist(point a,point b)
{
double dx=a.x-b.x,dy=a.y-b.y;
return dx*dx+dy*dy;
}
bool cmp(point a,point b)
{
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
double out_product(double x1,double y1,double x2,double y2)
{
return x1*y2-x2*y1;
}
vector<point> convex_hull(point* ps,int n)
{
sort(ps,ps+n,cmp); //很多题目都需要对乱序输入排序
int k=0; // 凸包点的index
vector<point> qs(n*2); //构造凸包
//逆时针构造凸包
//构造凸包的下侧
for(int i=0;i<n;i++)
{
while(k>1 && out_product(qs[k-1].x-qs[k-2].x , qs[k-1].y-qs[k-2].y , ps[i].x-qs[k-1].x , ps[i].y-qs[k-1].y)<=0) k--;
qs[k++]=ps[i];
}
//继续构造凸包的上侧
for(int i=n-2,t=k;i>=0;i--)
{
while(k>t && out_product(qs[k-1].x-qs[k-2].x , qs[k-1].y-qs[k-2].y , ps[i].x-qs[k-1].x , ps[i].y-qs[k-1].y)<=0) k--; //这里有个k>t 和 k>1效果一样
qs[k++]=ps[i];
}
qs.resize(k-1);
return qs;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
point p;
scanf("%lf%lf",&p.x,&p.y);
ps[i]=p;
}
vector<point> qs=convex_hull(ps,n);
double res=0;
for(int i=0;i<qs.size();i++)
for(int j=0;j<i;j++)
res=max(res,s_dist(qs[i],qs[j]));
cout<<(int)res<<endl; //!!!不加int 或不用printf("%.0lf\n",res); 就会WA
return 0;
}
|
还有一种时间复杂度更低的方法
convex hull + rotating calipers
这是一种常见、经典的方法
对踵点:如果凸包上过两个点画两条平行线,使凸包所有的点都夹在这两条线之间,这两个点就叫对踵点,称为一对对踵点对
对于一个凸包,最远距离一定是对踵点对
所以先找一对对踵点对,根据判断凹凸性,确定哪个点向后面的点移动(如图)(图懒得画),宏观来看就是对踵点对的连线旋转了180°
这样就总时间复杂度就是O(√n)
代码如下
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
|
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
struct point
{
double x,y;
}ps[50005];
inline double s_dist(point a,point b)
{
double dx=a.x-b.x,dy=a.y-b.y;
return dx*dx+dy*dy;
}
bool cmp(point a,point b)
{
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
inline double out_product(double x1,double y1,double x2,double y2)
{
return x1*y2-x2*y1;
}
vector<point> convex_hull(point* ps,int n)
{
sort(ps,ps+n,cmp); //很多题目都需要对乱序输入排序
int k=0; // 凸包点的index
vector<point> qs(n*2); //构造凸包
//逆时针构造凸包
//构造凸包的下侧
for(int i=0;i<n;i++)
{
while(k>1 && out_product(qs[k-1].x-qs[k-2].x , qs[k-1].y-qs[k-2].y , ps[i].x-qs[k-1].x , ps[i].y-qs[k-1].y)<=0) k--;
qs[k++]=ps[i];
}
//继续构造凸包的上侧
for(int i=n-2,t=k;i>=0;i--)
{
while(k>t && out_product(qs[k-1].x-qs[k-2].x , qs[k-1].y-qs[k-2].y , ps[i].x-qs[k-1].x , ps[i].y-qs[k-1].y)<=0) k--; //这里有个k>t 和 k>1效果一样
qs[k++]=ps[i];
}
qs.resize(k-1);
return qs;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
point p;
scanf("%lf%lf",&p.x,&p.y);
ps[i]=p;
}
vector<point> qs=convex_hull(ps,n);
double res=0;
int m=qs.size();
if(m==2) //特殊处理凸包退化情况
{
cout<<(int)s_dist(qs[0],qs[1])<<endl;
return 0;
}
int i=0,j=0; //表示左右俩对踵点
//求x轴方向上的对踵点对
for(int k=0;k<m;k++)
if(cmp(qs[j],qs[k])) j=k;
//rotating calipers
int si=i,sj=j;
while(!(i==sj && j==si)) //旋转180°,注意判断条件
{
//cout<<i<<" "<<j<<endl;
res=max(res,s_dist(qs[i],qs[j])); //这条语句放在while循环体的前端,可以把x轴方向上的对踵点对都比较
//通过外积判断凹凸性,判断是i移到i+1,还是j移到j+1
if(out_product(qs[(i+1)%m].x-qs[i].x , qs[(i+1)%m].y-qs[i].y , qs[(j+1)%m].x-qs[j].x , qs[(j+1)%m].y-qs[j].y)<0) //<0 或<=0都可以
i=(i+1)%m; //把m错写成n,tle好久
else
j=(j+1)%m; //!!!要%m 这样转一圈才能回到起点,退出循环
} //之前添加的debug条件忘记屏蔽,WA了特久
cout<<(int)res<<endl;
return 0;
}
|
===========================================================================================================
bfs dfs
2386
题解
bfs dfs都行
基础dfs
时间复杂度 O(mn)
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
|
//dfs
#include<iostream>
using namespace std;
char field[105][105];
int n,m;
void dfs(int x,int y)
{
field[x][y]='.';
for(int dx=-1;dx<=1;dx++)
for(int dy=-1;dy<=1;dy++)
{
int nx=x+dx,ny=y+dy;
if(0<=nx && nx<n && 0<=ny && ny<m && field[nx][ny]=='W')
dfs(nx,ny);
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
cin>>field[i][j];
}
int res=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
if(field[i][j]=='W')
{
dfs(i,j);
res++;
}
}
cout<<res<<endl;
return 0;
}
|
总结
经典dfs就是递归思想
如果图外面是可达且连通的,就在图外面加一圈
===========================================================================================================
状态压缩dp
2686
题解
TSP问题
不能在多项式时间内求解
暴力求解是阶乘数量级的
在数据不是很大的时候可以用状态压缩dp
对于这道题时间复杂度O((2^n)*m*m*n)
基础状态压缩dp
以3.667为例
道路图为
dp[S][x]
S表示剩余车票集合
x表示从a出发到达x(单源最短路)
dp表示最小花费
这样就变成DAG求最短路问题,不需要使用dijkstra算法,用dp就可以
dp的初始条件是
dp=inf , dp[U][a]=0
dp的状态转移方程
dp[S/{i}][u] = min{dp[S][v]+d[v][u]/t[i] (i∈S)}
将集合用整数的二进制表示
根据状态转移方程,可以用记忆化搜索+递归
这个问题可以用循环嵌套求解
约束条件很重要
这道题的约束条件是“车票”
如果题目要求每个点只能走一遍的话
那么又有一个约束条件是“点”(即判断这个点是否走过)
循环嵌套的写法就是按照套路
遍历所有子集,嵌套遍历每种子集的操作情况
遍历集合的所有情况(逆序)
嵌套遍历每个点
再嵌套遍历每个点
依题意再嵌套遍历剩下的所有车票(顺序可与上一步调换)
最后遍历所有的dp[S’][b] S’⊆U,获得最小值
ac代码
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
|
#include<iostream>
#include<cstring>
#define maxn 8
#define maxm 30
#define inf 0x3f3f3f3f
using namespace std;
int n,m,p,a,b;
int t[maxn];
int d[maxm][maxm];
double dp[1<<maxn][maxm]; //dp[剩下的车票集合][目标城市]=从a到达目标城市的最短时间
int main()
{
while(scanf("%d%d%d%d%d",&n,&m,&p,&a,&b)==5 and !(n==0 and m==0 and p==0 and a==0 and b==0))
{
for(int i=0;i<n;i++) scanf("%d",&t[i]);
memset(d,-1,sizeof(d));
int temp1,temp2,temp3;
for(int i=0;i<p;i++)
{
scanf("%d%d%d",&temp1,&temp2,&temp3);
d[temp1-1][temp2-1]=temp3;
d[temp2-1][temp1-1]=temp3;
}
for(int i=0;i<1<<n;i++) fill(dp[i],dp[i]+m,inf); //注意这里要用循环初始化二维数组 //inf是因为要用到min
dp[(1<<n)-1][a-1]=0;
double res=inf; //inf是因为要用到min
for(int s=(1<<n)-1;s>=0;s--)
{
res=min(res,dp[s][b-1]);
for(int v=0;v<m;v++)
{
for(int u=0;u<m;u++)
{
if(d[v][u]>=0)
{
for(int i=0;i<n;i++)
{
if(s>>i&1)
{
dp[s&~(1<<i)][u]=min(dp[s&~(1<<i)][u],dp[s][v]+(double)d[u][v]/t[i]);
}
}
}
}
}
}
if(res==inf) printf("Impossible\n");
else printf("%.3lf\n",res); //discuss里面说poj用lf会wa,用f就ac ,但我试了lf也可以ac
}
return 0;
}
|
===========================================================================================================
强连通分量
2816
题解
如果一头牛被其他所有牛认为是红牛,那么和它在同一个强连通分量的所有牛都
是红牛,所以scc分解,拓扑序最后一个强连通分量是红牛群,最后检查这个连通
块中的一头红牛是否对所有牛可达
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
|
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int V,m;
int t1,t2;
#define maxv 10005
vector<int> G[maxv];
vector<int> rG[maxv];
vector<int> vs;
bool used[maxv];
int cmp[maxv];
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]]) rdfs(rG[v][i],k);
}
}
int kosaraju_scc(){
memset(used,0,sizeof(used));
vs.clear();
for(int i=0;i<V;i++) if(!used[i]) dfs(i);
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--) if(!used[vs[i]]) rdfs(vs[i],k++);
return k;
}
int main(){
cin>>V>>m;
while(m--){
cin>>t1>>t2;
add_edge(t1-1,t2-1);
}
int k=kosaraju_scc();
int u=0,num=0; //备选个数 u取得正序最后一个
for(int i=0;i<V;i++){
if(cmp[i]==k-1){
u=i;
num++;
}
}
memset(used,0,sizeof(used));
rdfs(u,0); //反向判断是否都可达
for(int i=0;i<V;i++){
if(!used[i]){
num=0;
break;
}
}
cout<<num<<endl;
return 0;
}
|
===========================================================================================================
扫描线
2932
题意
坐标上有n个不相交的圆,求最外层圆的index
题解
由于数据规模,暴力超时
sweeping line
一般有两种,平移扫描,环形扫描
对于这一题,从左到右平移扫描
用一个容器维护每个圆的左右两个端点,代表扫描到圆和扫描出圆
对于扫描到的圆,判断它是否在别的圆内
只需要判断上下最近的两个圆(可画图证明,不严谨)
用一个容器维护还没扫描出的最外圆,可以排序,再查找。总时间复杂度O(nlgn)
可以选用set
当扫描到右时,把圆从set中去除,意味着扫描过了
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
|
#include<iostream>
#include<set>
#include<vector>
#include<algorithm>
using namespace std;
double x[40005],y[40005],r[40005];
int n;
typedef pair<double ,int> pdi;
bool inside(int i,int j)
{
double dx=x[i]-x[j],dy=y[i]-y[j];
return dx*dx+dy*dy<=r[j]*r[j];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
scanf("%lf%lf%lf",&r[i],&x[i],&y[i]);
vector<pdi> vt; //存左右两边界
for(int i=0;i<n;i++)
{
vt.push_back(make_pair(x[i]-r[i],i));
vt.push_back(make_pair(x[i]+r[i],i+n));
}
sort(vt.begin(),vt.end());
//扫描
set<pdi> outers; //set为了排序
vector<int> res; //存放结果
for(int i=0;i<vt.size();i++)
{
int id=vt[i].second%n;
if(vt[i].second<n) //扫描到左
{
set<pdi>::iterator it=outers.lower_bound(make_pair(y[id],id));
if(it!=outers.end() && inside(id,it->second)) continue; //上面最近的圆
if(it!=outers.begin() && inside(id,(--it)->second)) continue; //下面最近的圆
res.push_back(id);
outers.insert(make_pair(y[id],id));
}
else //扫描到右
outers.erase(make_pair(y[id],id));
}
sort(res.begin(),res.end());
cout<<res.size()<<endl;
for(int i=0;i<res.size();i++)
printf("%d%c",res[i]+1,i+1==res.size()? '\n' : ' ');
return 0;
}
|
===========================================================================================================
贪心
3069
题意
一条路上有n个路灯,每个路灯都能照亮左右的一段距离,问最少需要多少路灯才能使街道都亮着
题解
greedy
从最左开始向右延伸r,在r的范围内将最右的路灯点亮,此时这盏路灯将照亮左边和右边,从暗处的最左路灯开始,已知重复下去。
时间复杂度 O(n)
!!!!需要将路灯位置排序
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
|
#include<iostream>
#include<algorithm>
using namespace std;
int r,n;
int x[1006];
int main()
{
while(scanf("%d%d",&r,&n)==2 && r!=-1)
{
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
sort(x,x+n); // !!!记得排序
int j=0,ans=0;
while(j<n)
{
int s=x[j++];
while(j<n && x[j]<=s+r) j++;
int p=x[j-1];
while(j<n && p+r>=x[j]) j++;
ans++;
}
printf("%d\n",ans);
}
return 0;
}
|
===========================================================================================================
贪心
3617
题意
给一个字符串s和空字符串p,每次进行以下操作之一
删除s的头部字符,加入到p的尾部
删除s的尾部字符,加入到p的尾部
最后s为空,p的字典序最小
题解
greedy
时间复杂度最坏 O($n^2$)
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<iostream>
using namespace std;
int n;
char s[2005];
int c=0;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>s[i];
}
int a=0,b=n-1;
bool left=false;
while(a<=b)
{
for(int i=0;a+i<=b;i++)
{
if(s[a+i]<s[b-i])
{
left=true;
break;
}
else if(s[a+i]>s[b-i])
{
left=false;
break;
}
}
if(left) putchar(s[a++]);
else putchar(s[b--]);
c++;
if(c%80==0) putchar('\n');
}
return 0;
}
|
===========================================================================================================
01背包问题
3624
题解
基础01背包问题
如果用二维数组写的话就会MLE
猜测测试数据可能不会满,就随着数据动态申请内存,想侥幸过(可以用new/delete 或 malloc/free)结果还是MLE
这是MLE代码
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<iostream>
using namespace std;
//int dp[3410][12885];
int n,m;
//int w[3410];
//int v[3410];
int main()
{
cin>>n>>m;
int **dp,*w,*v;
dp=new int*[n+3];
for(int i=0;i<n+3;i++)
dp[i]=new int[m+3];
w=new int[n+3];
v=new int[n+3];
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i],&v[i]);
}
//initialized
for(int i=0;i<=n;i++)
dp[i][0]=0;
for(int i=0;i<=m;i++)
dp[0][i]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
cout<<dp[n][m];
delete dp,w,v;
return 0;
}
|
在discuss找了一圈也没有二维数组过的
优化成一维数组(节省内存,容易出bug)
AC代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include<iostream>
using namespace std;
int n,m;
int dp[12885];
int w[3410];
int v[3410];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[m];
return 0;
}
|
这个内层循环是递减的,如果是递增则解决完全背包问题
由于01背包问题的dp[i]只依赖于dp[i-1],内层循环递减时,dp[j]依赖的dp[j]为i-1的dp[j]
由于原来在二维数组中就是递增的,是同一个i,所以内层循环递增时,dp[j]依赖的dp[j]为i的dp[j],符合完全背包问题
由于有两种状态(i和i-1),所以可利用奇偶性滚动数组实现
AC代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include<iostream>
using namespace std;
int n,m;
int dp[2][12885];
int w[3410];
int v[3410];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
if(j<w[i])
dp[i&1][j]=dp[(i-1)&1][j];
else
dp[i&1][j]=max(dp[(i-1)&1][j],dp[(i-1)&1][j-w[i]]+v[i]);
cout<<dp[n&1][m];
return 0;
}
|
===========================================================================================================
2-SAT
3683
题解
xi表示某一婚礼
xi为真表示在开始举行
xi为假表示在结束举行
对于两个婚礼
如果同在开始时间举行会产生冲突
则 ¬xi ∨ ¬xj 为真
遍历所有的关系对,检查是否产生冲突
如果产生冲突则有一个析取式成立
遍历完之后得到一个合取范式
这样就把问题转换成2-SAT
根据蕴含等值式
a ∨ b ⇒ (a ∨ b) ∧ (b ∨ a) ⇒ (¬a → b) ∧ (¬b → a)
对于每一个蕴含式都可以构造一条有向边
最后scc分解
判断合取范式是否是可满足式,只需要检查¬xi 和 xi 是否在同一个强连通分量中
最后对于所在强连通分量的拓扑序,依次给出真值
ac代码
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
|
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int n;
int V;
const int maxv=2005;
vector<int> G[maxv];
vector<int> rG[maxv];
vector<int> vs;
bool used[maxv];
int cmp[maxv];
int s[1005],t[1005],d[1005];
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]]) rdfs(rG[v][i],k);
}
}
int kosaraju_scc(){
memset(used,0,sizeof(used));
vs.clear();
for(int i=0;i<V;i++) if(!used[i]) dfs(i);
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--) if(!used[vs[i]]) rdfs(vs[i],k++);
return k;
}
int main(){
cin>>n;
int t1,t2,t3,t4,t5;
for(int i=0;i<n;i++){
scanf("%d:%d%d:%d%d",&t1,&t2,&t3,&t4,&t5);
s[i]=t1*60+t2;
t[i]=t3*60+t4;
d[i]=t5;
}
V=n*2;
for(int i=0;i<n;i++)for(int j=i+1;j<n;j++){
if( min( s[i]+d[i] , s[j]+d[j] ) > max(s[i] , s[j])){
add_edge(i,n+j);
add_edge(j,n+i);
}
if(min(s[i]+d[i] , t[j]) > max(s[i] , t[j]-d[j])){
add_edge(i,j);
add_edge(n+j,i+n);
}
if(min(t[i] , s[j]+d[j]) > max(s[j] , t[i]-d[i])){
add_edge(i+n,j+n);
add_edge(j,i);
}
if(min(t[i] , t[j]) > max(t[i]-d[i] , t[j]-d[j])){
add_edge(n+i,j);
add_edge(n+j,i);
}
}
kosaraju_scc();
for(int i=0;i<n;i++){
if(cmp[i]==cmp[i+n]){
cout<<"NO\n";
return 0;
}
}
cout<<"YES\n";
for(int i=0;i<n;i++){
if(cmp[i]>cmp[i+n]){
printf("%02d:%02d %02d:%02d\n",s[i]/60,s[i]%60,(s[i]+d[i])/60,(s[i]+d[i])%60);
}
else{
printf("%02d:%02d %02d:%02d\n",(t[i]-d[i])/60,(t[i]-d[i])%60,t[i]/60,t[i]%60);
}
}
return 0;
}
|
===========================================================================================================
bfs dfs
3984
题解
dfs/bfs都行
基础bfs
(这个测试点只有一个,就是样例)
时间复杂度O(mn)
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<map>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
int n,m;
int maze[7][7];
int d[7][7];
const int inf=0x3f3f3f3f;
typedef pair<int,int> pii; //表示坐标
int sx,sy,gx,gy; //起点终点
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
map<pii,pii > mp; //记录前驱节点,目的是记录路径 (如果记录路径就可以不用求最短距离)
vector<pii > vt; //记录路径
void bfs()
{
queue<pii> q;
fill(d[0],d[0]+7*7,inf);
q.push(pii(sx,sy));
d[sx][sy]=0;
while(q.size())
{
pii p=q.front();
q.pop();
if(p.first==gx and p.second==gy) break; //很重要的终止判断条件
for(int i=0;i<4;i++)
{
int nx=p.first+dx[i],ny=p.second+dy[i];
if(nx>=0 and ny>=0 and nx<n and ny<m and maze[nx][ny]==0 and d[nx][ny]==inf) //inf的判断很重要
{
q.push(pii(nx,ny));
d[nx][ny]=d[p.first][p.second]+1;
mp[pii(nx,ny)]=pii(p.first,p.second);
}
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
n=5;
m=5;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>maze[i][j];
sx=0,sy=0,gx=4,gy=4;
bfs();
//cout<<d[gx][gy]<<endl;
pii p;
p=pii(gx,gy);
while(p!=pii(sx,sy))
{
vt.push_back(p);
p=mp[p];
}
reverse(vt.begin(),vt.end());
printf("(%d, %d)\n",sx,sy);
for(vector<pii>::iterator it=vt.begin();it!=vt.end();it++)
{
printf("(%d, %d)\n",it->first,it->second);
}
return 0;
}
|
总结
一个经典的bfs模板就是用队列来控制广度优先
===========================================================================================================
强连通分量
1236
题解
第一问就是求scc的数量,第二问就是求对每个scc出度为0的数量和入度为0的数量
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
|
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int maxv = 105;
int V;
vector<int> G[maxv];
vector<int> rG[maxv];
vector<int> vs;
bool used[maxv];
int cmp[maxv];
int in[maxv],out[maxv];
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]]) rdfs(rG[v][i],k);
}
}
int kosaraju_scc(){
memset(used,0,sizeof(used));
vs.clear();
for(int i=0;i<V;i++) if(!used[i]) dfs(i);
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--) if(!used[vs[i]]) rdfs(vs[i],k++);
return k;
}
int main(){
cin>>V;
for(int i=0;i<V;i++){
while(1){
int tmp;
cin>>tmp;
if(tmp==0) break;
tmp--;
add_edge(i,tmp);
}
}
int k = kosaraju_scc();
for(int i=0;i<V;i++){
for(int j=0;j<G[i].size();j++){
int to = G[i][j];
if(cmp[i] != cmp[to]){
in[cmp[to]]++;
out[cmp[i]]++;
}
}
}
int ind =0;
int outd = 0;
for(int i=0;i<k;i++){
if(in[i]==0) ind++;
if(out[i]==0) outd++;
}
int ans;
if(k==1) ans = 0;else ans = max(ind,outd);
cout<<ind<<endl;
cout<<ans<<endl;
return 0;
}
|
===========================================================================================================
平面点分治
3714
题解
懒得写
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
|
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cmath>
using namespace std;
typedef double db ;
struct node{
db x, y;
int flag;
}vt[200005], tmp[200005];
bool compy(node n1, node n2){
return n1.y < n2.y;
}
bool compx(node n1, node n2){
return n1.x < n2.x;
}
db dist(node n1, node n2){
db dx = fabs(n1.x - n2.x);
db dy = fabs(n1.y - n2.y);
return sqrt(dx * dx + dy * dy);
}
db solve(int l, int r){
if(l == r) return 1e50;
int m = (l + r) / 2;
db d = min(solve(l, m), solve(m + 1, r));
int cnt = 0;
for(int i = l; i <= r; ++i){
if(abs(vt[i].x - vt[m].x) < d){
tmp[++cnt] = vt[i];
}
}
sort(tmp + 1, tmp + cnt + 1, compy);
for(int i = 1; i <= cnt; ++i){
for(int j = i + 1; j <= cnt; ++j){
if(tmp[j].y - tmp[i].y >= d) break;
if(tmp[j].flag == tmp[i].flag) continue;
d = min(d, dist(tmp[i], tmp[j]));
}
}
return d;
}
int main() {
int _;
cin >> _;
while (_--) {
int n;
cin >> n;
for(int i = 1; i <= n; ++i){
cin >> vt[i].x >> vt[i].y;
vt[i].flag = 0;
}
for(int i = n + 1; i <= 2 * n; ++i){
cin >> vt[i].x >> vt[i].y;
vt[i].flag = 1;
}
sort(vt + 1, vt + 2 * n + 1, compx);
cout << fixed << setprecision(3) << solve(1, 2 * n) << endl;
}
return 0;
}
|