整理的算法模板合集: ACM模板
目录
题目传送门
题目总体情况
-
A 题:数论 + 动态规划
-
B 题:计算几何 + 最短路
-
C 题:模拟
-
D 题:图论(求连通块个数)
-
E 题:数据结构
-
F 题:二分答案
-
G 题:图论
-
H 题:略
-
I 题:搜索(BFS)/ 并查集
-
J 题:二维前缀和 + 二维差分
-
K 题:数学(难)
A、Intelligent Warehouse
题目大意:
给你一个序列,让你选出最多的数,使得选中的数之间互为倍数(
a
i
a_i
ai是
a
j
a_j
aj的倍数或者
a
j
a_j
aj是
a
i
a_i
ai的倍数)
思路:
数据2e5,求的是最多的个数,考虑DP
官方题解:
方法一:直接暴力转移方程(注意,如果把范围设置为10000000才能AC,大一点都会TLE)
#include <cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 10000007;
int n;
int f[N];
int cnt[N];
int maxv;
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n; ++ i){
int x;
scanf("%d", &x);
cnt[x] ++;
}
int maxv = 10000000;
int ans = 0;
for(int i = 1 ;i <= maxv; ++ i){
if(cnt[i]){
f[i] += cnt[i];
for(int j = 2 * i; j <= maxv; j += i){
f[j] = max(f[j], f[i]);
}
ans = max(ans, f[i]);
}
}
printf("%d\n", ans);
return 0;
}
方法二(优化):
按照题解里的方法,筛出来所有的素数,然后再枚举所有的素数,因为任意合数都可以用素数凑出来(唯一分解定理)
B、Intelligent Robot
题目大意:给你一个 n * m 的迷宫,迷宫里有k堵乱放的墙,你不能穿过墙,求给定两点间的最短距离。
就是一个板子题嘛,一个月没碰计算几何神TM我一开始叉乘写错了,找了半天…
有用的点只有开始和结束的点以及所有墙的端点,因为不能穿过墙但是可以蹭着墙皮走,也就是说端点是可以走的,所以我们枚举所有墙的端点,如果能直接到达就连边,求一次最短路即可。(两点之间线段最短嘛)
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
//typedef __int128 int;oj上可用,编译器上不可用
const int N = 500007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-8;
const ll mod = 19260817;
typedef pair<int, int >PII;
typedef pair<double, int>PDI;
struct Point {
double x, y;
Point(double x = 0, double y = 0):x(x), y(y){ }
double operator ^ (const Point&p)const{return x*p.y-y*p.x;}
}point[N];
typedef Point Vector;
struct Line {
Point a, b;
}line[N];
int n, m, k;
bool vis[N];
double dist[N];
int head[N], ver[M], nex[M], tot;
double edge[M];
Vector operator + (Point a, Point b){return Point(a.x + b.x, a.y + b.y);}
Vector operator - (Point a, Point b){return Vector(a.x - b.x, a.y - b.y);}
Vector operator * (Vector a, double p){return Vector(a.x * p, a.y * p);}
double get_dist(Point a, Point b)
{
double xx = a.x - b.x;
double yy = a.y - b.y;
return sqrt(xx * xx + yy * yy);
}
int dcmp(double x){
if(fabs(x) < eps) return 0;
else return x < 0 ? -1 : 1;
}
//重载等于运算符
bool operator == (const Point& a, const Point& b){return !dcmp(a.x - b.x) && !dcmp(a.y - b.y);}
double Dot(Vector A, Vector B){return A.x * B.x + A.y * B.y;}
double Cross(Vector A, Vector B){return A.x * B.y - A.y * B.x;}
void add(int x, int y, double z)
{
ver[tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot ++ ;
}
bool segment_proper_intersection(Point a1, Point a2, Point b1, Point b2)
{
double c1 = Cross(a2 - a1, b1 - a1), c2 = Cross(a2 - a1, b2 - a1);
double c3 = Cross(b2 - b1, a1 - b1), c4 = Cross(b2 - b1, a2 - b1);
return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
}
void init()
{
memset(head, -1, sizeof head);
memset(vis, 0, sizeof vis);
tot = 0;
}
void dijkstra(int s, int n)
{
for(int i = 1; i <= n; ++ i){
dist[i] = 1e12;
}
dist[s] = 0.0;
priority_queue<PDI, vector<PDI>, greater<PDI> >q;
q.push({0.0, s});
while(q.size()){
int x = q.top().second;
q.pop();
if(vis[x])continue;
vis[x] = 1;
for(int i = head[x]; ~i; i = nex[i]){
int y = ver[i];
double z = edge[i];
if(dist[y] - eps >= dist[x] + z){
dist[y] = dist[x] + z;
q.push({dist[y], y});
}
}
}
}
int main()
{
init();
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= k; ++ i){
scanf("%lf%lf%lf%lf", &line[i].a.x, &line[i].a.y, &line[i].b.x, &line[i].b.y);
}
for(int i = 1; i <= k; ++ i){
point[2 * i - 1] = line[i].a;
point[2 * i] = line[i].b;
}
scanf("%lf%lf%lf%lf", &point[0].x, &point[0].y, &point[2 * k + 1].x, &point[2 * k + 1].y);
for(int i = 0; i <= 2 * k + 1; ++ i){//只需要走端点才是最近的
for(int j = i + 1; j <= 2 * k + 1; ++ j){
bool flag = true;
for(int s = 1; s <= k; ++ s){
if(segment_proper_intersection(point[i], point[j], line[s].a, line[s].b)){
flag = false;
break;
}
}
if(flag){
add(i, j, get_dist(point[i], point[j]));
add(j, i, get_dist(point[i], point[j]));
}
}
}
dijkstra(0, 2 * k + 1);
printf("%.4f\n", dist[2 * k + 1]);
return 0;
}
C、Smart Browser
水题模拟,队友写的。
#include <iostream>
#include <cstring>
using namespace std;
const int N=1e5+10;
char a[N];
int main()
{
cin>>a;
int num,ans=0;
for(int i=0;i<strlen(a);i++)
{
int num=0;
while(a[i]=='w')
{
ans++;
num++;
i++;
}
if(num) ans+=num-1;
}
cout<<ans<<endl;
return 0;
}
D、Router Mesh
题目大意:求分别把每个点删除以后连通块的个数
几乎就是一个tarjan模板题了,(但是我因为最近在玩莫比乌斯反演,快一个月没怎么碰图论了,导致这道题竟然还卡了我一会…)
官方题解:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
int n, m;
int dfn[N], low[N], tim;
int ver[M], nex[M], edge[M], head[N], tot;
int ans;
int cut[N];
int num[N];//把这个点删除以后能分成多少块
void add(int x,int y){
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
}
void tarjan(int x, int rt){//计算该割点连接了多少个连通块
dfn[x] = low[x] = ++ tim;
int flag = 0;
int cnt = 0;//(割开这个点会将图分成cnt个连通块)
for(int i = head[x]; ~i;i = nex[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y, rt);
low[x] = min(low[x], low[y]);
if(dfn[x] <= low[y]){//该割点返回了一条独立的路
cnt ++ ;
flag ++ ;
if(x != rt || flag > 1)cut[x] = 1;
}
}
else low[x] = min(low[x], dfn[y]);
}
if(x != rt)cnt ++ ;//如果不是根的话说明该点的上面应该可以割出来一个连通块
num[x] = cnt;
}
int main(){
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
memset(dfn, 0, sizeof dfn);
tot = tim = 0;
for(int i = 1;i <= m;++i){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
ans = 0;
int cnt = 0;
for(int i = 1;i <= n;++ i){//数据从0到n - 1
if(!dfn[i]){
cnt ++ ;//可能有孤立点(孤立连通块)
tarjan(i, i);
}
}
//cnt是当前连通块的个数
for(int i = 1; i <= n; ++ i){
if(head[i] == -1){
printf("%d ", cnt - 1);
}
else
printf("%d ", num[i] + cnt - 1);
}
puts("");
}
E、Phone Network
F、Design Problemset
题目大意:制作一个题集,题目数量要在[L,R]
之间,并且每个题集的每种题需要
L
i
[
i
]
,
R
i
[
i
]
L_i[i],R_i[i]
Li[i],Ri[i] 道题。其中每种题有a[i]
道,题集要求把所有的题都用完,求最多能做多少个题集。
大致算了一下,中间的结果最大可以达到
1
0
23
10^{23}
1023,会爆long long…真狠。所以我们可以使用__int128
来存(甚至可以水高精)
官方题解:
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef __int128 ll;
//__int128, __int128_t
//#define int _int128
const int N = 500007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int mod = 80112002;
typedef pair<int, int >PII;
__int128 read(){
__int128 x=0,f=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1;
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f*x;
}
void print(__int128 x){
if(x<0)putchar('-'),x=-x;
if(x>9)print(x/10);//注意这里是x>9不是x>10 (2019.10 wa哭了回来标记一下)
putchar(x%10+'0');
}
ll k, LL, RR, L[N], R[N];
ll a[N], minv;
ll sumL, sumR;
bool check(ll A)
{
ll need = A * (LL - sumL);
ll sumA = 0;
for(int i = 1; i <= k; ++ i)
if(a[i] < A * L[i])return false;
for(int i = 1; i <= k ; ++ i){
sumA += min(a[i] - L[i] * A, (R[i] - L[i]) * A);
}
return sumA >= need;
}
int main()
{
k = read(), LL = read(), RR = read();
ll l = 0, r = 0, ans = 0;
for(int i = 1; i <= k; ++ i){
a[i] = read();
r += a[i];
}
for(int i = 1; i <= k; ++ i){
L[i] = read(), R[i] = read();
sumL += L[i], sumR += R[i];
}
if(sumL > RR || sumR < LL){
puts("0");
return 0;
}
while(l <= r){
ll mid = l + r >> 1;
if(check(mid))ans = mid, l = mid + 1;
else r = mid - 1;
}
print(ans);
return 0;
}
G、Tree Projection
H、Grouping
I、Walking Machine
**题目大意:**一个
n
∗
m
n*m
n∗m的迷宫,给你每个点能够走的方向,求能够出去的点的个数。
并查集过的,之前有一道题洛谷上的奶酪,跟这道题差不多,那道题就是用bfs或者并查集可过,我的dfs剪枝超时,所以试了一下并查集就过了。我用并查集只是多了阿尔法 α ( n ∗ m ) < 5 \alpha(n*m)<5 α(n∗m)<5的常数,所以同样可以过。
官方题解:
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 5000007, M = 50007, INF = 5000007;
const double eps = 1e-6;
int n, m;
int fa[N];
string s[N];
int ans ;
int Find(int x)
{
if(fa[x] == x) return x;
return fa[x] = Find(fa[x]);
}
void unions(int x, int y)
{
int fx = Find(x), fy = Find(y);
fa[fx] = fy;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n * m; ++ i)
fa[i] = i;
fa[INF] = INF;
for(int i = 0; i < n; ++ i){
cin >> s[i];
}
//cout << "ok" << endl;
for(int i = 0; i < n; ++ i){
for(int j = 0; j < m; ++ j){
if(s[i][j] == 'W'){
if(i - 1 < 0)unions(i * m + j, INF);
else unions(i * m + j, (i - 1) * m + j);
}
else if(s[i][j] == 'S'){
if(i + 1 >= n)unions(i * m + j, INF);
else unions(i * m + j, (i + 1) * m + j);
}
else if(s[i][j] == 'A'){
if(j - 1 < 0)unions(i * m + j, INF);
else unions(i * m + j, i * m + j - 1);
}
else if(s[i][j] == 'D'){
if(j + 1 >= m)unions(i * m + j, INF);
else unions(i * m + j, i * m + j + 1);
}
}
}
//cout << "ok" << endl;
for(int i = 0; i < n; ++ i){
for(int j = 0; j < m; ++ j){
if(Find(i * m + j) == INF)
ans ++ ;
}
}
printf("%d\n", ans);
}
J、Matrix Subtraction
题意:有一个
n
∗
m
n * m
n∗m 的矩阵 M,通过反复选择
a
∗
b
a*b
a∗b大小的子矩阵 并且使子矩阵中的所有元素都减去 1 ,问最终矩阵 M 能否变为全 0。如果可能,在一行中打印 ^_^
,否则在一行中打印 QAQ
。
思路:
我们直接
n
2
n^2
n2暴力枚举
a
∗
b
a*b
a∗b的子矩阵,每一块都减去 当前值 使之变为 0,利用二维差分可以快速实现这个操作。如果中途出现了负数或最后不全为 0,则输出 QAQ
。(本来想的是用二维树状数组,结果写了半个小时调不出来…)注意
官方题解:
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 2007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-6;
int a[N][N];
int n, m, A, B;
void add(int x_1, int y_1, int x_2, int y_2, int z){
a[x_1][y_1] += z;
a[x_2 + 1][y_1] -= z;
a[x_1][y_2 + 1] -= z;
a[x_2 + 1][y_2 + 1] += z;
}
bool check()
{
for(int i = 1; i <= n; ++ i){
for(int j = 1 ; j <= m; ++ j){
if(a[i][j] != 0)
return false;
}
}
return true;
}
int t;
int main()
{
scanf("%d", &t);
while(t -- ){
memset(a, 0, sizeof a);
scanf("%d%d%d%d", &n, &m, &A, &B);
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= m; ++ j){
int x;
scanf("%d", &x);
add(i, j, i, j, x);
}
}
bool success = 1;
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= m; ++ j){
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
if(a[i][j] < 0){success = 0;break;}
int ii = i + A - 1, jj = j + B - 1;
if(ii <= n && jj <= m)
add(i, j, ii, jj, -a[i][j]);
}
if(success == 0)break;
}
if(success == 0){
puts("QAQ");
continue;
}
if(check())puts("^_^");
else puts("QAQ");
}
return 0;
}