void quick_sort(int a[], int l, int r)
{
if(l >= r) return;
int x = a[(l+r)>>1], i = l-1, j = r+1;
while(i < j) {
do i++; while(a[i] < x);
do j--; while(a[j] > x);
if(i < j) swap(a[i],a[j]);
}
quick_sort(a,l,j);
quick_sort(a,j+1,r);
}
void merge_sort(int a[], int l, int r)
{
if(l >= r) return;
int mid = l+r>>1;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int k = 0, i = l, j = mid+1;
while(i <= mid && j <= r) {
if(a[i] <= a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while(i <= mid) tmp[k++] = a[i++];
while(j <= r) tmp[k++] = a[j++];
for(int x = 0, y = l;y <= r;x++, y++) a[y] = tmp[x];
}
bool check(int x) // 检查x是否满足某种性质
{
}
// 区间[l,r]被划分成[l,mid]和[mid+1,r]时使用
int binary_search(int l, int r)
{
while(l < r)
{
int mid = (l+r)>>1;
if(check(mid)) r = mid;
else l = mid+1;
}
return l;
}
const double eps = 1e-6;
bool check(double x) // 检查x是否满足某种性质
{
}
double binary_search(double l, double r)
{
while(r-l > eps)
{
double mid = (l+r)>>1;
if(check(mid)) r = mid;
else l = mid;
}
return l;
}
// C = A + B, A >= 0, B >= 0
vector add(vector &A, vector &B)
{
if (A.size() < B.size()) return add(B, A);
vector C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
// C = A - B, 满足A >= B, A >= 0, B >= 0
vector sub(vector &A, vector &B)
{
vector C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
// C = A * b, A >= 0, b >= 0
vector mul(vector &A, int b)
{
vector C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
// A / b = C ... r, A >= 0, b > 0
vector div(vector &A, int b, int &r)
{
vector C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
S[i, j] = 第i行j列格子左上部分所有元素的和
//以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
799. 最长连续不重复子序列 - AcWing题库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMzMJkaE-1629167397783)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210628145907363.png)]
#include
using namespace std;
const int MAXN = 1e5+50;
int a[MAXN], cnt[MAXN], dis[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
int ans = 1;
for(int i = 1,j = 1;j <= n;j++)
{
cnt[a[j]]++;
if(cnt[a[j]] > 1) {
for(int k = i;k <= dis[a[j]];k++) {
cnt[a[k]]--;
}
i = dis[a[j]]+1;
}
dis[a[j]] = j;
ans = max(ans,j-i+1);
}
printf("%d\n",ans);
return 0;
}
AcWing 800. 数组元素的目标和 - AcWing
#include
using namespace std;
const int N=100010;
int a[N],b[N];
int main()
{
int n,m,x;
cin>>n>>m>>x;
for(int i=0;i>a[i];
for(int i=0;i>b[i];
for(int i = 0, j = m-1;i < n;i++)
{
while(j >= 0 && a[i] + b[j] > x) j--;
if(a[i] + b[j] == x) {
printf("%d %d\n",i,j);
return 0;
}
}
return 0;
}
vector alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
802. 区间和 - AcWing题库
#include
using namespace std;
typedef pair PII;
const int MAXN = 3e5+50;
int n, m;
int a[MAXN], sum[MAXN];
vectorall;
vectoradd, query;
int find(int x)
{
int l = 0, r = all.size()-1;
while(l < r)
{
int mid = (l + r) >> 1;
if(all[mid] >= x) r = mid;
else l = mid+1;
}
return r+1;
}
vector::iterator unique(vector &a)
{
int j = 0;
for(int i = 0;i < a.size();i++)
{
if(!i && a[i] != a[i-1])
{
a[j++] = a[i];
}
}
return a.begin()+j;
}
int main()
{
scanf("%d%d",&n,&m);
while(n--)
{
int x, c;
scanf("%d%d",&x,&c);
add.push_back({x,c});
all.push_back(x);
}
while(m--)
{
int l, r;
scanf("%d%d",&l,&r);
query.push_back({l,r});
all.push_back(l);
all.push_back(r);
}
sort(all.begin(),all.end());
all.erase(unique(all.begin(),all.end()),all.end());
for(auto it : add)
{
int u = find(it.first);
a[u] += it.second;
}
for(int i = 1;i <= all.size();i++)
{
sum[i] = sum[i-1]+a[i];
}
for(auto it : query)
{
int l = find(it.first);
int r = find(it.second);
int res = sum[r]-sum[l-1];
printf("%d\n",res);
}
return 0;
}
#include
using namespace std;
typedef pair PII;
const int MAXN = 3e5 + 50;
int a[MAXN], sum[MAXN];
vectorall;
vectoradd, query;
int x, c;
int main()
{
int n, m;
scanf("%d%d",&n,&m);
while(n--)
{
scanf("%d%d",&x,&c);
add.push_back({x,c});
all.push_back(x);
}
while(m--)
{
int l, r;
scanf("%d%d",&l,&r);
query.push_back({l,r});
all.push_back(l);
all.push_back(r);
}
sort(all.begin(),all.end());
all.erase(unique(all.begin(),all.end()),all.end());
for(auto it : add)
{
int u = lower_bound(all.begin(),all.end(),it.first)-all.begin();
a[u] += it.second;
}
for(int i = 1;i <= all.size();i++)
{
sum[i] = sum[i-1] + a[i];
}
for(auto it : query)
{
int l = lower_bound(all.begin(),all.end(),it.first)-all.begin();
int r = lower_bound(all.begin(),all.end(),it.second)-all.begin();
int ans = sum[r]-sum[l-1];
printf("%d\n",ans);
}
}
// 将所有存在交集的区间合并
void merge(vector &segs)
{
vector res;
sort(segs.begin(), segs.end());
int st = -2e9, ed = -2e9;
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second);
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
左端点排序
#include
using namespace std;
const int MAXN = 1e5+40;
struct node{
int l, r;
friend bool operator<(node &a, node b)
{
return a.l < b.l;
}
};
vectorN;
int main()
{
int n;
scanf("%d",&n);
int re = n;
while(n--)
{
int l, r;
scanf("%d%d",&l,&r);
N.push_back({l,r});
}
sort(N.begin(),N.end());
int cnt = 1;
int ir = N[0].r;
for(int i = 1;i < re;i++)
{
if(N[i].l > ir) {
cnt++;
ir = N[i].r;
}
else ir = max(ir,N[i].r);
}
printf("%d\n",cnt);
return 0;
}
vector, 变长数组,倍增的思想
size() 返回元素个数
empty() 返回是否为空
clear() 清空
front()/back()
push_back()/pop_back()
begin()/end()
[]
支持比较运算,按字典序
pair
first, 第一个元素
second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素
priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue, greater> q;
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
保证栈顶元素是栈中最大/最小的。
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
830. 单调栈 - AcWing题库
#include
using namespace std;
const int MAXN = 1e5+50;
int stk[MAXN], a[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d",&a[i]);
int tt = 0;
for(int i = 0;i < n;i++)
{
while(tt && a[i] <= stk[tt-1])
{
tt--;
}
if(tt == 0) printf("-1%c", n == 0 ? '\n' : ' ');
else printf("%d%c",stk[tt-1],n == 0 ? '\n' : ' ');
stk[tt++] = a[i];
}
//printf("\n");
return 0;
}
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头,hh++
while (hh <= tt && check(q[tt], i)) tt -- ; // 判断队尾是否单调,tt--
q[ ++ tt] = i;
}
154. 滑动窗口 - AcWing题库
#include
using namespace std;
const int MAXN = 1e6+50;
int a[MAXN];
dequeq1,q2;
int main()
{
int n, k;
scanf("%d%d",&n,&k);
for(int i = 0;i < n;i++) scanf("%d",&a[i]);
for(int i = 0;i < k;i++)
{
while(!q1.empty() && a[i] <= a[q1.back()]) q1.pop_back();
q1.push_back(i);
}
printf("%d ",a[q1.front()]);
for(int i = k;i < n;i++)
{
while(!q1.empty() && a[i] <= a[q1.back()]) q1.pop_back();
while(!q1.empty() && i - q1.front() + 1 > k) q1.pop_front();
q1.push_back(i);
printf("%d ",a[q1.front()]);
}
printf("\n");
for(int i = 0; i < k;i++)
{
while(!q2.empty() && a[i] >= a[q2.back()]) q2.pop_back();
q2.push_back(i);
}
printf("%d ",a[q2.front()]);
for(int i = k;i < n;i++)
{
while(!q2.empty() && a[i] >= a[q2.back()]) q2.pop_back();
while(!q2.empty() && i - q2.front() + 1 > k) q2.pop_front();
q2.push_back(i);
printf("%d ",a[q2.front()]);
}
printf("\n");
return 0;
}
手写单调队列
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e6+50;
int n, k;
int a[MAXN], q[MAXN];
int main()
{
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
int hh = 0, tt = -1; // hh是头, tt是尾
for(int i = 1;i <= n;i++)
{
while(hh <= tt && i - q[hh] + 1 > k) hh++;
while(hh <= tt && a[i] <= a[q[tt]]) tt--;
q[++tt] = i;
if(i >= k) printf("%d ",a[q[hh]]);
}
printf("\n");
hh = 0, tt = -1;
for(int i = 1;i <= n;i++)
{
while(hh <= tt && i - q[hh] + 1 > k) hh++;
while(hh <= tt && a[i] >= a[q[tt]]) tt--;
q[++tt] = i;
if(i >= k) printf("%d ",a[q[hh]]);
}
printf("\n");
return 0;
}
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
831. KMP字符串 - AcWing题库
#include
using namespace std;
const int MAXN = 1e6+50;
int ne[MAXN];
char p[MAXN], s[MAXN];
int n, m;
int main()
{
cin >> n;
cin >> p+1;
cin >> m;
cin >> s+1;
for(int i = 2, j = 0;i <= n;i++)
{
while(j && p[i] != p[j+1]) j = ne[j];
if(p[i] == p[j+1]) j++;
ne[i] = j;
}
for(int i = 1, j = 0;i <= m;i++)
{
while(j && s[i] != p[j+1]) j = ne[j];
if(s[i] == p[j+1]) j++;
if(j == n)
{
j = ne[j];
printf("%d ",i-n);
}
}
printf("\n");
return 0;
}
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
void init()
{
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
841. 字符串哈希 - AcWing题库
#include
using namespace std;
const int MAXN = 1e5+50;
const int P = 131;
char str[MAXN];
int n, m;
typedef unsigned long long ULL;
ULL h[MAXN], p[MAXN]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
void init()
{
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",str+1);
init();
while(m--)
{
int l1, r1, l2, r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1) == get(l2,r2)) puts("Yes");
else puts("No");
}
return 0;
}
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
837. 连通块中点的数量 - AcWing题库
#include
using namespace std;
const int MAXN = 1e5+50;
int fa[MAXN], sz[MAXN];
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
int n, m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
fa[i] = i;
sz[i] = 1;
}
while(m--)
{
string op;
int a, b;
cin >> op;
if(op == "Q2")
{
scanf("%d",&a);
a = find(a);
printf("%d\n",sz[a]);
}
else if(op == "Q1")
{
scanf("%d%d",&a,&b);
if(find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
scanf("%d%d",&a,&b);
a = find(a);
b = find(b);
if(a != b)
{
fa[a] = b;
sz[b] += sz[a];
}
}
}
return 0;
}
int son[N][26], cnt[N], idx = 0;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
void insert(string str)
{
int p = 0;
for(int i = 0;str[i] != '\0';i++)
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p]++;
}
// 查询字符串出现的次数
int query(string str)
{
int p = 0;
for(int i = 0;str[i] != '\0';i++)
{
int u = str[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
835. Trie字符串统计 - AcWing题库
#include
using namespace std;
const int MAXN = 2e5+50;
char s[MAXN];
int cnt[MAXN], son[MAXN][28];
int idx = 0;
void insert(string str)
{
int p = 0;
for(int i = 0;str[i] != '\0';i++)
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p]++;
}
int query(string str)
{
int p = 0;
for(int i = 0;str[i] != '\0';i++)
{
int u = str[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
string op;
cin >> op;
if(op == "I")
{
string s;
cin >> s;
insert(s);
}
else {
string s;
cin >> s;
printf(
"%d\n",query(s));
}
}
return 0;
}
143. 最大异或对 - AcWing题库
最大异或和
给定一个序列,求序列中两个元素的异或最大值。
字典树进行优化 O ( n l o g n ) O(nlogn) O(nlogn)
每次往与当前位相反的方向走,保证异或和最大。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, a[MAXN], son[MAXN*30][2], idx;
void insert(int x)
{
int p = 0;
for(int i = 30;i >= 0;i--)
{
int u = x >> i & 1;
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x)
{
int p = 0, res = 0;
for(int i = 30;i >= 0;i--)
{
int u = x >> i & 1;
if(son[p][!u]) {
p = son[p][!u];
res += 1 << i;
}
else p = son[p][u];
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
insert(a[i]);
}
int res = 0;
for(int i = 1;i <= n;i++) res = max(res,query(a[i]));
printf("%d\n",res);
return 0;
}
// 朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点 + 路径压缩
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
// 维护集合size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
// 维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
1250. 格子游戏 - AcWing题库
并查集解决的是连通性(无向图联通分量)和传递性(家谱关系)问题,并且可以动态的维护。抛开格子不看,任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通,于是可以将点分为若干个集合,每个集合对应图中的一个连通块。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 40050;
int n, m, res = 1e9;
int a[MAXN], fa[MAXN];
int transfer(int x, int y) // 二维转换为一维
{
return (x-1) * n + y;
}
void init()
{
for(int i = 1;i <= n*n;i++)
{
fa[i] = i;
}
}
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d%d",&n,&m);
init();
for(int i = 1;i <= m;i++)
{
int x, y;
char op;
scanf("%d %d %c",&x,&y,&op);
int res1, res2;
res1 = transfer(x,y);
if(op == 'D') res2 = transfer(x+1,y);
else if(op == 'R') res2 = transfer(x,y+1);
int u = find(res1), v = find(res2);
if(u == v) {
res = min(res,i);
}
else {
fa[u] = v;
}
}
if(res != 1e9) printf("%d\n",res);
else puts("draw");
return 0;
}
1252. 搭配购买 - AcWing题库
并查集+01背包
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 10010;
int fa[MAXN], c[MAXN], d[MAXN], dp[MAXN];
int n, m, w, id;
struct node{
int c, d;
}Node[MAXN];
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void init()
{
for(int i = 1;i <= n;i++) fa[i] = i;
}
int main()
{
scanf("%d%d%d",&n,&m,&w);
init();
for(int i = 1;i <= n;i++)
{
scanf("%d%d",&c[i],&d[i]);
}
for(int i = 1;i <= m;i++)
{
int a, b;
scanf("%d%d",&a,&b);
int u = find(a), v = find(b);
if(u != v)
{
fa[v] = u;
c[u] += c[v];
d[u] += d[v];
}
}
for(int i = 1;i <= n;i++) {
if(i == fa[i])
{
Node[id].c = c[i];
Node[id].d = d[i];
id++;
}
}
for(int i = 0;i < id;i++)
{
for(int j = w;j >= Node[i].c;j--)
{
dp[j] = max(dp[j],dp[j-Node[i].c]+Node[i].d);
}
}
int ans = dp[w];
printf("%d\n",ans);
return 0;
}
237. 程序自动分析 - AcWing题库
并查集+离散化
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 2e6+50;
int n, T, id, cnt;
int fa[MAXN];
vector>equ, iequ;
int all[MAXN];
void init()
{
for(int i = 0;i <= cnt;i++) fa[i] = i;
}
int search(int x)
{
int res = lower_bound(all,all+cnt,x)-all;
return res;
}
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d",&T);
while(T--)
{
equ.clear();
iequ.clear();
bool flag = true;
scanf("%d",&n);
for(int k = 0;k < n;k++)
{
int i, j, e;
scanf("%d%d%d",&i,&j,&e);
if(e == 1) equ.push_back(make_pair(i,j));
else iequ.push_back(make_pair(i,j));
all[id++] = i;
all[id++] = j;
}
sort(all,all+id);
cnt = unique(all,all+id)-all;
init();
for(auto i : equ)
{
int x = search(i.first), y = search(i.second);
int u = find(x), v = find(y);
fa[u] = v;
}
for(auto i : iequ)
{
int x = search(i.first), y = search(i.second);
int u = find(x), v = find(y);
if(u == v) {
flag = false;
break;
}
}
if(flag) puts("YES");
else puts("NO");
}
return 0;
}
基于二进制优化
x = 2 i k + 2 i k = 1 + 2 i k − 2 + ⋯ + 2 i 1 log x ≥ i k ≥ i k − 1 ≥ i k − 2 ≥ ⋯ ≥ i 1 x = 2^{i_k}+2^{i_{k=1}}+2^{i_{k-2}}+\dots +2^{i_1} \qquad \qquad \log x \ge i_k \ge i_{k-1} \ge i_{k-2} \ge \dots \ge i_1 x=2ik+2ik=1+2ik−2+⋯+2i1logx≥ik≥ik−1≥ik−2≥⋯≥i1
① ( x − 2 i 1 , x ] (x-2^{i_1},x] (x−2i1,x]
② ( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2},x-2^{i_1}] (x−2i1−2i2,x−2i1]
③ ( x − 2 i 1 − 2 i 2 − 2 i 3 , x − 2 i 1 − 2 i 2 ] (x-2^{i_1}-2^{i_2}-2^{i_3},x-2^{i_1}-2^{i_2}] (x−2i1−2i2−2i3,x−2i1−2i2]
… \dots …
k ( x − 2 i 1 − 2 i 2 − ⋯ − 2 i k = 0 , x − 2 i 1 − 2 i 2 − ⋯ − 2 i k − 1 ] (x-2^{i_1}-2^{i_2}-\dots -2^{i_k} = 0,x-2^{i_1}-2^{i_2}-\dots -2^{i_{k-1}}] (x−2i1−2i2−⋯−2ik=0,x−2i1−2i2−⋯−2ik−1]
发现区间 ( L , R ] (L,R] (L,R]的区间长度为R二进制的最后一位1 所对应的次幂 l o w b i t ( R ) lowbit(R) lowbit(R)
区间为 [ R − l o w b i t ( R ) + 1 , R ] [R-lowbit(R)+1,R] [R−lowbit(R)+1,R]
记 c [ x ] c[x] c[x]为区间 a [ x − l o w b i t ( x ) + 1 , x ] a[x-lowbit(x)+1,x] a[x−lowbit(x)+1,x]所有数的和
c i = a i + a i − 1 + … … + a i − l o w b i t ( i ) + 1 c_i = a_i+a_{i-1}+……+a_{i-lowbit(i)+1} ci=ai+ai−1+……+ai−lowbit(i)+1
c[1] = a[1]
c[2] = a[2]+a[1]
c[3] = a[3]
c[4] = a[4]+a[3]+a[2]+a[1]
eg 查询a[1]+a[2]+……+a[7]
a[7] = c[7] a[6]+a[5] = c[6] a[4]+a[3]+a[2]+a[1] = c[4]
7 = ( 111 ) 2 (111)_2 (111)2 6 = ( 110 ) 2 (110)_2 (110)2 4 = ( 100 ) 2 (100)_2 (100)2 每次减去lowbit
// 查询a[1~x]的和
int sum(int x)
{
int ret = 0;
while(x)
{
ret += c[x];
x -= lowbit(x);
}
return ret;
}
x的父亲结点是x+lowbit(x)
每次修改会影响这个节点到根路径下的节点
// a[i] += k
void change(int i, int k)
{
while(i <= n)
{
c[i] += k;
i += lowbit(i);
}
}
inline int lowbit(int x)
{
return x & (-x);
}
inline int lowbit(int x)
{
return x & (-x);
}
// 查询a[1~x]的和
int sum(int x)
{
int ret = 0;
while(x)
{
ret += c[x];
x -= lowbit(x);
}
return ret;
}
// a[i] += k
void change(int i, int k)
{
while(i <= n)
{
c[i] += k;
i += lowbit(i);
}
}
采用差分数组+树状数组
区间修改相当于差分数组中修改两个点
单点查询相当于差分数组求前缀和
区间修改 : 差分数组 a [ L , R ] + = c a[L,R] += c a[L,R]+=c 等价于 b [ R + 1 ] − = c b [ L ] − = c b[R+1] -= c \qquad b[L] -= c b[R+1]−=cb[L]−=c
区间求和 : a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] a[1]+a[2]+a[3]+\dots+a[x] a[1]+a[2]+a[3]+⋯+a[x] = ∑ i = 1 x a [ i ] = ∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i=1}^xa[i] = \sum_{i=1}^{x} \sum_{j=1}^i b[j] ∑i=1xa[i]=∑i=1x∑j=1ib[j]
考虑补集
a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] = ( x + 1 ) ∗ ( b [ 1 ] + b [ 2 ] + ⋯ + b [ x ] ) − ( 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + 3 ∗ b [ 3 ] + ⋯ + x ∗ b [ x ] ) a[1]+a[2]+a[3]+\dots+a[x] = (x+1)*(b[1]+b[2]+\dots+b[x])-(1*b[1]+2*b[2]+3*b[3]+\dots+x*b[x]) a[1]+a[2]+a[3]+⋯+a[x]=(x+1)∗(b[1]+b[2]+⋯+b[x])−(1∗b[1]+2∗b[2]+3∗b[3]+⋯+x∗b[x])
前面为 b [ i ] b[i] b[i]的前缀和,后面为 i ∗ b [ i ] i*b[i] i∗b[i]的前缀和
// a[x][y] += z;
int update(int x, int y, int z)
{
int i = x;
while(i <= n)
{
int j = y;
while(j <= m)
{
c[i][j] += z;
j += lowbit(j);
}
i += lowbit(i);
}
}
// a[1][1] + …… + a[1][y] + a[2][1] + …… a[2][n] + …… +a[x][1] + …… + a[x][y]
int sum(int x, int y)
{
int res = 0, i = x;
while(i > 0)
{
j = y;
while(j > 0)
{
res += c[i][j];
j -= lowbit(j);
}
i -= lowbit(i);
}
return res;
}
一个完全二叉树,每个节点代表一个区间。
区间 [ L , R ] [L,R] [L,R]被分为 [ L , m i d ] , [ m i d + 1 , R ] [L,mid],[mid+1,R] [L,mid],[mid+1,R] m i d = ⌊ L + R 2 ⌋ mid = \lfloor \frac{L+R}{2} \rfloor mid=⌊2L+R⌋
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHksIY0k-1629167397787)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210322084255533.png)]
线段树 当前结点x
父节点 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor ⌊2x⌋
左儿子 x < < 1 x << 1 x<<1
右儿子 x < < 1 ∣ 1 x << 1 | 1 x<<1∣1
最大范围 n < < 2 n << 2 n<<2
五个操作
struct node{
int l, r;
ll sum, lazy; // 区间[l,r]的和
};
int n, m;
int w[MAXN];
node tr[MAXN << 2];
// 求父节点的和
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) // 将懒标记下传
{
ll lazy = tr[u].lazy;
if(lazy)
{
tr[u << 1].lazy += lazy;
tr[u << 1 | 1].lazy += lazy;
tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
tr[u].lazy = 0; // 父节点的懒标记重置为0
}
}
//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
if(l == r)
{
tr[u] = {l,r,w[r]};
return;
}
else
{
tr[u] = {l,r,0};
int mid = (l+r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else
{
pushdown(u); // 下放懒标记
int mid = (tr[u].l + tr[u].r) >> 1;
ll sum = 0;
if(l <= mid) sum += query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
if(tr[u].l == tr[u].r) tr[u].sum += v;
else
{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1|1, x, v);
pushup(u);
}
}
// a[l]~a[r] += d
void change(int u, int l, int r, int d) // 区间修改
{
if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
{
tr[u].lazy += d;
tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
return;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, d);
if(r > mid) change(u << 1 | 1, l, r, d);
pushup(u);
}
}
struct node{
int l, r;
ll sum, lazy; // 区间[l,r]的和
};
int n, m;
int w[MAXN];
node tr[MAXN << 2];
// 求父节点的和
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) // 将懒标记下传
{
ll lazy = tr[u].lazy;
if(lazy)
{
tr[u << 1].lazy += lazy;
tr[u << 1 | 1].lazy += lazy;
tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
tr[u].lazy = 0; // 父节点的懒标记重置为0
}
}
//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
if(l == r)
{
tr[u] = {l,r,w[r]};
return;
}
else
{
tr[u] = {l,r,0};
int mid = (l+r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else
{
pushdown(u); // 下放懒标记
int mid = (tr[u].l + tr[u].r) >> 1;
ll sum = 0;
if(l <= mid) sum += query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
if(tr[u].l == tr[u].r) tr[u].sum += v;
else
{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1|1, x, v);
pushup(u);
}
}
// a[l]~a[r] += d
void change(int u, int l, int r, int d) // 区间修改
{
if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
{
tr[u].lazy += d;
tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
return;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, d);
if(r > mid) change(u << 1 | 1, l, r, d);
pushup(u);
}
}
这种存图方式的数据结构主要是边集数组,顾名思义,图的边是用数组来存储的。
当然想要完美表示图结构,光有一个边集数组还不够,还要有一个数组存储指向每一个点的第一条边的“指针”。
而每一条边都需要存储接下来一条边的“指针”,这样就能够像类似邻接表一样方便遍历每一个点的所有边了。
#include
#include
// 最大顶点数
const int V = 100000;
// 最大边数
const int E = 100000;
// 边结构体的定义
struct Edge {
int to; // 表示这条边的另外一个顶点
int next; // 指向下一条边的数组下标,值为-1表示没有下一条边
};
// head[i] 表示顶点`i`的第一条边的数组下标,-1表示顶点`i`没有边
int head[V];
Edge edge[E];
// 链式前向星初始化,只需要初始化顶点数组就可以了
memset(head, -1, sizeof(head));
// 增加边的方式
// 新增边 a -> b,该边的数组下标为`id`
inline void addedge(int a, int b, int id)
{
edge[id].to = b;
edge[id].next = head[a]; // 新增的边要成为顶点`a`的第一条边,而不是最后一条边
head[a] = id++;
return;
}
// 遍历从`a`点出去的所有边
for (int i=head[a]; i!=-1; i=e[i].next) {
// e[i] 就是你当前遍历的边 a -> e[i].to
}
int dfs(int u)
{
vis[u] = true;
for(int i = head[u];i != -1;i = edge[i].next)
{
int p = edge[i].to();
if(vis[p]) continue;
// 处理结点
}
}
dfs(1);
846. 树的重心 - AcWing题库
#include
using namespace std;
const int MAXN = 2e5 + 50;
int head[MAXN];
bool vis[MAXN];
struct Edge{
int to, next;
}edge[MAXN];
int n, id, ans;
inline void addedge(int a, int b)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
id++;
}
// 以u为根的子树中节点个数
int dfs(int u)
{
int res = 0, sum = 1;
vis[u] = true;
for(int i = head[u];i != -1;i = edge[i].next) // 遍历树种结点
{
int p = edge[i].to;
if(vis[p]) continue;
int son = dfs(p);
res = max(res, son);
sum += son;
}
res = max(res,n-sum); // 剩余点构成子树的节点个数
ans = min(ans,res);
return sum;
}
int main()
{
scanf("%d",&n);
memset(vis,false,sizeof(vis));
memset(head,-1,sizeof(head));
for(int i = 1;i <= n-1;i++)
{
int a, b;
scanf("%d%d",&a,&b);
addedge(a,b);
addedge(b,a);
}
ans = n;
dfs(1);
printf("%d\n",ans);
return 0;
}
queue q;
vis[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = edge[i].next)
{
int p = edge[i].to;
if(vis[p]) continue;
vis[p] = true; // 表示点j已经被遍历过
q.push(p);
}
}
847. 图中点的层次 - AcWing题库
#include
using namespace std;
const int MAXN = 2e5+50;
const int inf = 0x3f3f3f3f;
int head[MAXN];
struct Edge{
int to, next;
}edge[MAXN];
bool vis[MAXN];
int dist[MAXN];
int n, m, id;
queueq;
inline void addedge(int a, int b)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
id++;
return;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) dist[i] = inf;
while(m--)
{
int a, b;
scanf("%d%d",&a,&b);
addedge(a,b);
}
q.push(1);
vis[1] = true;
dist[1] = 0;
while(!q.empty())
{
int p = q.front();
q.pop();
for(int i = head[p];i != -1;i = edge[i].next)
{
int u = edge[i].to;
if(vis[u]) continue;
vis[u] = true;
dist[u] = min(dist[u],dist[p]+1);
q.push(u);
}
}
if(dist[n] == inf) printf("-1\n");
else printf("%d\n",dist[n]);
return 0;
}
848. 有向图的拓扑序列 - AcWing题库
#include
#include
#include
#include
#include
#include
const int MAXN = 2e5+50;
using namespace std;
int n, m, id;
int head[MAXN];
struct Edge{
int to, next;
}edge[MAXN];
bool vis[MAXN];
int ans[MAXN];
int d[MAXN]; // 存储入度
queueq;
inline void addedge(int a, int b)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
id++;
return;
}
bool topsort()
{
int cnt = 0;
for(int i = 1;i <= n;i++) {
if(d[i] == 0) {
q.push(i);
}
}
while(!q.empty())
{
int u = q.front();
q.pop();
ans[cnt++] = u;
for(int i = head[u];i != -1;i = edge[i].next)
{
int p = edge[i].to;
d[p]--;
if(d[p] == 0) q.push(p);
}
}
if(cnt < n) return false;
else return true;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
while(m--)
{
int a, b;
scanf("%d%d",&a,&b);
addedge(a,b);
d[b]++;
}
bool flag = topsort();
if(flag == true) {
for(int i = 0;i < n;i++) printf("%d ",ans[i]);
}
else printf("-1\n");
return 0;
}
时间复杂度 O ( n 2 + m ) O(n^2+m) O(n2+m)
int dijkstra()
{
memset(dist,INF,sizeof(dist));
dist[1] = 0;
for(int i = 1;i <= n-1;i++)
{
int t = -1;
for(int j = 1;j <= n;j++)
{
if(!vis[j] && (t == -1 || dist[j] < dist[t]))
{
t = j;
}
}
vis[t] = true;
for(int k = head[t];k != -1;k = edge[k].next)
{
int u = edge[k].to;
if(vis[u]) continue;
if(dist[u] > dist[t] + edge[k].w)
{
dist[u] = dist[t] + edge[k].w;
}
}
}
if(dist[n] == INF) return -1;
else return dist[n];
}
849. Dijkstra求最短路 I - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5 + 50;
const int INF = 0x3f3f3f3f;
int head[MAXN], dist[MAXN];
bool vis[MAXN];
struct Edge{
int to, next, w;
}edge[MAXN];
int n, m, id;
inline void addedge(int a, int b, int w)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
edge[id].w = w;
id++;
return;
}
int dijkstra()
{
memset(dist,INF,sizeof(dist));
dist[1] = 0;
for(int i = 1;i <= n-1;i++) // 一共进行n-1次操作
{
int t = -1;
for(int j = 1;j <= n;j++) // 找出最小值
{
if(!vis[j] && (t == -1 || dist[j] < dist[t]))
{
t = j;
}
}
vis[t] = true;
for(int k = head[t];k != -1;k = edge[k].next) // 遍历所有以此顶点为起始的边
{
int u = edge[k].to;
if(vis[u]) continue;
if(dist[u] > dist[t] + edge[k].w)
{
dist[u] = dist[t] + edge[k].w;
}
}
}
if(dist[n] == INF) return -1;
else return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
while(m--)
{
int a, b, w;
scanf("%d%d%d",&a,&b,&w);
addedge(a,b,w);
}
int ans = dijkstra();
printf("%d\n",ans);
}
时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn), n n n 表示点数, m m m 表示边数
int dijkstra()
{
for(int i = 1;i <= n;i++) dist[i] = 0x3f3f3f3f;
dist[1] = 0;
q.push({0,1});
while(!q.empty())
{
auto u = q.top();
q.pop();
int dis = u.first;
int idx = u.second;
if(vis[idx]) continue;
vis[idx] = true;
for(int i = head[idx]; i != -1;i = edge[i].next)
{
int p = edge[i].to;
if(dist[p] > dis + edge[i].w)
{
dist[p] = dis + edge[i].w;
q.push({dist[p],p}); // 优先队列存取最小的dist
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
850. Dijkstra求最短路 II - AcWing题库
#include
#include
#include
#include
#include
#include
using namespace std;
typedef pair PII;
const int MAXN = 2e5+50;
int head[MAXN];
struct Edge{
int to, next, w;
}edge[MAXN];
int dist[MAXN];
priority_queue,greater>q;
int n, m, id;
bool vis[MAXN];
inline void addedge(int a, int b, int w)
{
edge[id].to = b;
edge[id].next = head[a];
edge[id].w = w;
head[a] = id;
id++;
return;
}
int dijkstra()
{
for(int i = 1;i <= n;i++) dist[i] = 0x3f3f3f3f;
dist[1] = 0;
q.push({0,1});
while(!q.empty())
{
auto u = q.top();
q.pop();
int dis = u.first;
int idx = u.second;
if(vis[idx]) continue;
vis[idx] = true;
for(int i = head[idx]; i != -1;i = edge[i].next)
{
int p = edge[i].to;
if(dist[p] > dis + edge[i].w)
{
dist[p] = dis + edge[i].w;
q.push({dist[p],p});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
while(m--)
{
int a, b, w;
scanf("%d%d%d",&a,&b,&w);
addedge(a,b,w);
}
int ans = dijkstra();
printf("%d\n",ans);
return 0;
}
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
854. Floyd求最短路 - AcWing题库
#include
#include
#include
#include
#include
const int INF = 1e9;
using namespace std;
int n, m, k;
int dist[205][205];
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(i == j) dist[i][j] = 0;
else dist[i][j] = INF;
}
}
while(m--)
{
int x, y, z;
scanf("%d%d%d",&x,&y,&z);
dist[x][y] = min(dist[x][y],z);
}
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
while(k--)
{
int x, y;
scanf("%d%d",&x,&y);
if(dist[x][y] >= INF/2) printf("impossible\n"); // 因为存在负边权,所以大于INF/2也合理
else printf("%d\n",dist[x][y]);
}
return 0;
}
时间复杂度 O ( m n ) O(mn) O(mn)
有边数限制k
bool bellman_ford()
{
memset(dist,INF,sizeof(dist));
dist[1] = 0;
for(int i = 1;i <= k;i++)
{
memcpy(last,dist,sizeof(dist)); // 辅助数组存储上一轮结果,避免一次循环中进行多次改变
for(int j = 0;j < id;j++) // 遍历所有边进行更新
{
int u = edge[j].from;
int v = edge[j].to;
if(dist[v] > last[u] + edge[j].w)
{
dist[v] = last[u] + edge[j].w; // 松弛操作
}
}
}
// n次操作后满足三角不等式dist[a] <= dist[b] + w
if(dist[n] >= INF /2) return false;
else return true;
}
853. 有边数限制的最短路 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 10010;
const int INF = 0x3f3f3f3f;
int head[MAXN], dist[MAXN], last[MAXN];
struct Edge{
int from, next, to, w;
}edge[MAXN];
int n, m, k, id;
inline void addedge(int a, int b, int w)
{
edge[id].to = b;
edge[id].from = a;
edge[id].next = head[a];
head[a] = id;
edge[id].w = w;
id++;
return;
}
bool bellman_ford()
{
memset(dist,INF,sizeof(dist));
dist[1] = 0;
for(int i = 1;i <= k;i++)
{
memcpy(last,dist,sizeof(dist));
for(int j = 0;j < id;j++)
{
int u = edge[j].from;
int v = edge[j].to;
if(dist[v] > last[u] + edge[j].w)
{
dist[v] = last[u] + edge[j].w;
}
}
}
if(dist[n] >= INF /2) return false;
else return true;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
memset(head,-1,sizeof(head));
while(m--)
{
int a, b, w;
scanf("%d%d%d",&a,&b,&w);
addedge(a,b,w);
}
bool flag = bellman_ford();
if(flag) printf("%d\n",dist[n]);
else printf("impossible\n");
return 0;
}
条件:不存在负环
dist[b] = min(dist[b],dist[a]+w)
只有在dist[a]
变小时,dist[b]
才会变小
SPFA是基于这个进行优化,使用一个队列存储
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue q;
q.push(1);
st[1] = true;
while (q.size())
{
auto t = q.front();
q.pop();
st[t] = false; // 出队删除
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
851. spfa求最短路 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
const int INF = 0x3f3f3f3f;
int head[MAXN], dist[MAXN];
bool vis[MAXN];
struct Edge{
int to, next, w;
}edge[MAXN];
int n, m, id;
queueq;
inline void addedge(int a, int b, int w)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
edge[id].w = w;
id++;
return;
}
int spfa()
{
memset(dist,INF,sizeof(dist));
vis[1] = true;
dist[1] = 0;
q.push(1);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1;i = edge[i].next)
{
int p = edge[i].to;
if(dist[p] > dist[u] + edge[i].w)
{
dist[p] = dist[u] + edge[i].w;
if(!vis[p])
{
vis[p] = true;
q.push(p);
}
}
}
}
if(dist[n] == INF) return -1;
else return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
while(m--)
{
int x, y, z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
int ans = spfa();
if(ans == -1) printf("impossible\n");
else printf("%d\n",ans);
return 0;
}
bool spfa()
{
memset(cnt,0,sizeof(cnt));
memset(dist,INF,sizeof(dist));
dist[1] = 0;
vis[1] = true;
q.push(1);
for(int i = 1;i <= n;i++) {
vis[i] = true;
q.push(i);
}
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u];i != -1;i = edge[i].next)
{
int p = edge[i].to;
if(dist[p] > dist[u] + edge[i].w)
{
dist[p] = dist[u] + edge[i].w;
cnt[p] = cnt[u] + 1;
if(cnt[p] >= n) return true;
if(!vis[p])
{
vis[p] = true;
q.push(p);
}
}
}
}
return false;
}
852. spfa判断负环 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 10010;
const int INF = 0x3f3f3f3f;
int head[MAXN], cnt[MAXN], dist[MAXN];
bool vis[MAXN];
struct Edge{
int to, next, w;
}edge[MAXN];
queueq;
int n, m, id;
inline void addedge(int a, int b, int w)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id;
edge[id].w = w;
id++;
return;
}
bool spfa()
{
memset(cnt,0,sizeof(cnt));
memset(dist,INF,sizeof(dist));
dist[1] = 0;
vis[1] = true;
q.push(1);
for(int i = 1;i <= n;i++) {
vis[i] = true;
q.push(i);
}
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u];i != -1;i = edge[i].next)
{
int p = edge[i].to;
if(dist[p] > dist[u] + edge[i].w)
{
dist[p] = dist[u] + edge[i].w;
cnt[p] = cnt[u] + 1;
if(cnt[p] >= n) return true;
if(!vis[p])
{
vis[p] = true;
q.push(p);
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
while(m--)
{
int x, y, z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
bool flag = spfa();
if(flag) printf("Yes\n");
else printf("No\n");
return 0;
}
int n; // n表示点数
int g[N][N]; // 邻接矩阵,存储所有边
int dist[N]; // 存储其他点到当前最小生成树的距离
bool st[N]; // 存储每个点是否已经在生成树中
// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
858. Prim算法求最小生成树 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
const int INF = 0x3f3f3f3f;
int dist[MAXN];
bool vis[MAXN];
int w[505][505];
int n, m;
int prim()
{
int res = 0;
memset(dist,INF,sizeof(dist));
for(int i = 0;i < n;i++)
{
int t = -1;
for(int j = 1;j <= n;j++)
{
if(!vis[j] && (t == -1 || dist[t] > dist[j]))
{
t = j;
}
}
if(i && dist[t] == INF) return INF;
if(i) res += dist[t];
vis[t] = true;
for(int j = 1;j <= n;j++) dist[j] = min(dist[j],w[t][j]);
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(i == j) w[i][j] = 0;
else w[i][j] = INF;
}
}
while(m--)
{
int x, y, z;
scanf("%d%d%d",&x,&y,&z);
w[x][y] = w[y][x] = min(w[x][y],z);
}
int ans = prim();
if(ans == INF) printf("impossible\n");
else printf("%d\n",ans);
return 0;
}
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 2e5+50;
struct Edge{
int from, to, w;
bool operator<(const Edge &a)
{
return w < a.w;
}
}edge[MAXN];
int n, m;
int fa[MAXN];
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < m;i++)
{
int x, y, w;
scanf("%d%d%d",&x,&y,&w);
edge[i].from = x;
edge[i].to = y;
edge[i].w = w;
}
int res = 0, cnt = 0;
for(int i = 1;i <= n;i++) fa[i] = i;
sort(edge,edge+m);
for(int i = 0;i < m;i++)
{
int from = edge[i].from;
int to = edge[i].to;
int u = find(from), v = find(to);
if(u != v) {
cnt++;
res += edge[i].w;
fa[u] = v;
}
}
if(cnt < n-1) printf("impossible\n");
else printf("%d\n",res);
return 0;
}
int n; // n表示点数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色
// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (color[j] == -1)
{
if (!dfs(j, !c)) return false;
}
else if (color[j] == c) return false;
}
return true;
}
bool check()
{
memset(color, -1, sizeof color);
bool flag = true;
for (int i = 1; i <= n; i ++ )
if (color[i] == -1)
if (!dfs(i, 0))
{
flag = false;
break;
}
return flag;
}
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过
bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true;
if (match[j] == 0 || find(match[j]))
{
match[j] = x;
return true;
}
}
}
return false;
}
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
memset(st, false, sizeof st);
if (find(i)) res ++ ;
}
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
void get_primes(int n)
{
for(int i = 2;i <= n/i;i++)
{
int s = 0;
while(n % i == 0)
{
n /= i;
s++;
}
if(s) printf("%d %d\n",i,s);
}
if(n > 1) printf("%d 1\n",n);
printf("\n");
}
int primes[MAXN], cnt; // primes[]存储所有素数
bool vis[MAXN]; // vis[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!vis[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] * i <= n ; j ++ )
{
vis[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
void get_divisors(int n)
{
for(int i = 1;i * i <= n;i++)
{
if(n % i == 0)
{
res.push_back(i);
if(i * i != n) res.push_back(n/i);
}
}
sort(res.begin(),res.end());
}
如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
φ ( n ) \varphi(n) φ(n)表示 1 − N 1-N 1−N中与 N N N互质数的个数。
若在算数基本定理中, N = p 1 α 1 p 2 α 2 … p n α n N=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_n^{\alpha_n} N=p1α1p2α2…pnαn
则 φ ( N ) = N ∗ p 1 − 1 p 1 ∗ p 2 − 1 p 2 ⋯ ∗ p n − 1 p n \varphi(N) = N * \frac {p_1-1}{p_1} * \frac {p_2-1}{p_2} \dots * \frac {p_n-1}{p_n} φ(N)=N∗p1p1−1∗p2p2−1⋯∗pnpn−1
#include
#include
#include
#include
using namespace std;
int n;
int main()
{
scanf("%d",&n);
while(n--)
{
int x;
scanf("%d",&x);
int res = x;
for(int i = 2;i * i <= x;i++)
{
if(x % i == 0) {
res = res / i * (i-1);
while(x % i == 0) x /= i;
}
}
if(x > 1) res = res / x * (x-1);
printf("%d\n",res);
}
return 0;
}
原理
欧拉函数与分解质因数后质数的次数无关
因此 φ ( i ∗ p j ) \varphi(i*p_j) φ(i∗pj)可以分为两种情况, p j p_j pj为素数
若 i i i与 p j p_j pj互质
则 φ ( i ∗ p j ) = ( i ∗ p j ) ∗ ( 1 − 1 p 1 ) ∗ ⋯ ∗ ( 1 − 1 p j ) … ( 1 − 1 p k ) = φ ( i ) ∗ p j ∗ ( 1 − 1 p j ) = φ ( i ) ∗ ( p j − 1 ) \varphi(i*p_j) = (i*p_j)*(1-\frac{1}{p_1})* \dots * (1-\frac{1}{p_j}) \dots (1-\frac{1}{p_k}) = \varphi(i)*p_j*(1-\frac{1}{p_j}) = \varphi(i) * (p_j-1) φ(i∗pj)=(i∗pj)∗(1−p11)∗⋯∗(1−pj1)…(1−pk1)=φ(i)∗pj∗(1−pj1)=φ(i)∗(pj−1)
若 i i i中有 p j p_j pj这个因子
则 v a r p h i ( i ∗ p j ) = ( i ∗ p j ) ∗ ( 1 − 1 p 1 ) ∗ ⋯ ∗ ( 1 − 1 p j ) … ( 1 − 1 p k ) = φ ( i ) ∗ p j varphi(i*p_j) = (i*p_j)*(1-\frac{1}{p_1})* \dots * (1-\frac{1}{p_j}) \dots (1-\frac{1}{p_k}) = \varphi(i)*p_j varphi(i∗pj)=(i∗pj)∗(1−p11)∗⋯∗(1−pj1)…(1−pk1)=φ(i)∗pj
void get_euler(int n)
{
euler[1] = 1;
for(int i = 2;i <= n;i++)
{
if(!vis[i])
{
prime[cnt++] = i;
euler[i] = i-1;
}
for(int j = 0;prime[j] * i <= n;j++)
{
int t = i * prime[j];
vis[t] = true;
if(i % prime[j] == 0)
{
euler[t] = euler[i] * prime[j];
break;
}
euler[t] = euler[i] * (prime[j]-1);
}
}
}
若 a a a与 n n n互质,则有 a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)} \equiv 1 (mod \quad n) aφ(n)≡1(modn)
证明:
1~n 中, a 1 , a 2 , … , a φ ( n ) a_1,a_2,\dots, a_{\varphi(n)} a1,a2,…,aφ(n)为均与 n n n互质的数
同时因为 a a a与 n n n互质, a a 1 , a a 2 , … , a φ ( n ) aa_1,aa_2,\dots,a_{\varphi(n)} aa1,aa2,…,aφ(n)与 n n n互质
a a i ≡ a a j ≡ 1 m o d n aa_i \equiv aa_j \equiv 1 \mod n aai≡aaj≡1modn
a ( a i − a j ) ≡ 0 m o d n a(a_i-a_j) \equiv 0 \mod n a(ai−aj)≡0modn
a i ≡ a j m o d n a_i \equiv a_j \mod n ai≡ajmodn
所以 a 1 , a 2 , a 3 , … a φ ( n ) a_1,a_2,a_3,\dots a_{\varphi(n)} a1,a2,a3,…aφ(n) 与 a a 1 , a a 2 , a a 3 , … , a φ ( n ) aa_1,aa_2,aa_3,\dots, a_{\varphi(n)} aa1,aa2,aa3,…,aφ(n)只是 m o d n \mod n modn 下的一个重排
所以 a φ ( n ) ∗ a 1 ∗ a 2 ⋯ ∗ a v a r p h i ( n ) ≡ a 1 ∗ a 2 ⋯ ∗ a v a r p h i ( n ) m o d n a^{\varphi(n)}*a_1*a_2\dots *a_{varphi(n)}\equiv a_1*a_2\dots *a_{varphi(n)} \mod n aφ(n)∗a1∗a2⋯∗avarphi(n)≡a1∗a2⋯∗avarphi(n)modn
因此 a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)} \equiv 1 (mod \quad n) aφ(n)≡1(modn)
int qmi(int a, int b, int p)
{
int res = 1;
while(b)
{
if(b & 1) res = (ll) res * a % p;
b >>= 1;
a = (ll) a * a % p;
}
return res;
}
a b = a ∗ b − 1 m o d m \frac{a}{b} = a * b^{-1} \mod m ba=a∗b−1modm
b − 1 b^{-1} b−1为逆元
存在条件:b与m互质,即m为质数时b存在逆元
即是 b ∗ x ≡ 1 m o d n b*x \equiv 1 \mod n b∗x≡1modn x的解
根据费马小定理 b n − 1 ≡ 1 m o d n b^{n-1} \equiv 1 \mod n bn−1≡1modn
此时 b n − 2 b^{n-2} bn−2为 b b b的一个乘法逆元
对于任意正整数a,b。一定存在整数x,y,满足 a x + b y = g c d ( a , b ) ax+by = gcd(a,b) ax+by=gcd(a,b)
a x + b y = g c d ( a , b ) ax+by = gcd(a,b) ax+by=gcd(a,b)
当 b = 0 b=0 b=0时, g c d ( a , b ) = a gcd(a,b)=a gcd(a,b)=a。 x = 1 , y = 0 x=1,y=0 x=1,y=0是满足方程的一组解。
g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b) = gcd(b,a\,mod\,b) gcd(a,b)=gcd(b,amodb)。代入原式中,发现 b y + ( a − ⌊ a b ⌋ ∗ b ) ∗ x = g c d ( b , a m o d b ) by+(a-\lfloor \frac{a}{b} \rfloor*b)*x = gcd(b,a\,mod\,b) by+(a−⌊ba⌋∗b)∗x=gcd(b,amodb)
所以 b y + a ∗ ( y − ⌊ a b ⌋ ∗ x ) = g c d ( b , a m o d b ) by+a*(y-\lfloor \frac{a}{b} \rfloor*x) = gcd(b,a\,mod\,b) by+a∗(y−⌊ba⌋∗x)=gcd(b,amodb)
// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a/b) * x;
return d;
}
// a[N][N]是增广矩阵
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找到绝对值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 无解
return 1; // 有无穷多组解
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
// c[a][b] 表示从a个苹果中选b个的方案数
for (int i = 0; i < N; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
//首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
//如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int b, int p) // 快速幂
{
int res = 1;
while(b)
{
if(b & 1) res = (ll)res * a % p;
b >>= 1;
a = (ll)a * a % p;
}
return res;
}
void init()// 预处理阶乘的余数和阶乘逆元的余数
{
fact[0] = infact[0] = 1;
for(int i = 1;i <= 1e5+5;i++)
{
fact[i] = (ll)fact[i-1] * i % MOD;
infact[i] = (ll)infact[i-1] * qmi(i,MOD-2,MOD) % MOD;
}
}
若p是质数,则对于任意整数 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int qmi(int a, int k, int p) // 快速幂模板
{
int res = 1 % p;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b, int p) // 通过定理求组合数C(a, b)
{
if (a < b) return 0;
LL x = 1, y = 1; // x是分子,y是分母
for (int i = a, j = 1; j <= b; i --, j ++ )
{
x = (LL)x * i % p;
y = (LL) y * j % p;
}
return x * (LL)qmi(y, p - 2, p) % p;
}
int lucas(LL a, LL b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
1. 筛法求出范围内的所有质数
2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ...
3. 用高精度乘法将所有质因子相乘
int primes[N], cnt; // 存储所有质数
int sum[N]; // 存储每个质数的次数
bool st[N]; // 存储每个数是否已被筛掉
void get_primes(int n) // 线性筛法求素数
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p) // 求n!中的次数
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector mul(vector a, int b) // 高精度乘低精度模板
{
vector c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
get_primes(a); // 预处理范围内的所有质数
for (int i = 0; i < cnt; i ++ ) // 求每个质因数的次数
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
vector res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ ) // 用高精度乘法将所有质因子相乘
for (int j = 0; j < sum[i]; j ++ )
res = mul(res, primes[i]);
给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为:
Cat(n) = C(2n, n) / (n + 1)
890. 能被整除的数 - AcWing题库
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 20;
int n, m;
int primes[MAXN];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < m;i++) scanf("%d",&primes[i]);
ll res = 0;
for(int i = 1;i < 1 << m;i++)
{
int t = 1, cnt = 0;
for(int j = 0;j < m;j++)
{
if(i >> j & 1)
{
cnt++;
if((ll)t * primes[j] > n)
{
t = -1;
break;
}
t *= primes[j];
}
}
if(t != -1)
{
if(cnt % 2 == 1) res += n / t;
else res -= n / t;
}
}
printf("%d\n",res);
return 0;
}
给定N堆物品,第i堆物品有 A i A_i Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。
我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。
所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
NIM博弈不存在平局,只有先手必胜和先手必败两种情况。
定理: NIM博弈先手必胜,当且仅当 A 1 A_1 A1 ^$ A_2$ ^ … ^ A n A_n An ≠ \ne = 0
若一个游戏满足:
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。
设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:
mex(S) = min{x}, x属于自然数,且x不属于S
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。
设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
893. 集合-Nim游戏 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int N = 110, M = 11010;
int s[N], f[M];
int n, k;
int sg(int x)
{
if(f[x] != -1) return f[x];
unordered_set S;
for(int i = 0;i < k;i++)
{
if(x >= s[i]) S.insert(sg(x-s[i]));
}
for(int i = 0;;i++)
{
if(S.count(i) == 0) {
f[x] = i;
return f[x];
}
}
}
int main()
{
memset(f,-1,sizeof(f));
scanf("%d",&k);
for(int i = 0;i < k;i++) scanf("%d",&s[i]);
scanf("%d",&n);
int res = 0;
for(int i = 0;i < n;i++)
{
int x;
scanf("%d",&x);
res ^= sg(x);
}
if(res) puts("Yes");
else puts("No");
return 0;
}
由于 f [ i ] f[i] f[i]只用到了 f [ i − 1 ] f[i-1] f[i−1],且第二位背包容量不超过 j j j,可以从二维变成一维。
#include
#include
#include
using namespace std;
int N, V;
const int MAXN = 1010;
int v[MAXN], w[MAXN], dp[MAXN];
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++) scanf("%d%d",&v[i],&w[i]);
for(int i = 1;i <= N;i++)
{
for(int j = V;j >= v[i];j--)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
int ans = dp[V];
printf("%d\n",ans);
return 0;
}
以上时间复杂度为 O ( n 3 ) O(n^3) O(n3),可以优化为 O ( n 2 ) O(n^2) O(n2)
#include
#include
#include
using namespace std;
const int MAXN = 1010;
int v[MAXN], w[MAXN], dp[MAXN];
int N, V;
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++) scanf("%d%d",&v[i],&w[i]);
for(int i = 1;i <= N;i++)
{
for(int j = v[i];j <= V;j++)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
printf("%d\n",dp[V]);
return 0;
}
f[i][j] = max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]) k = 0, 1, 2, ………,s[i]
#include
#include
#include
using namespace std;
const int MAXN = 25000, M = 2010;
int N, V, cnt;
int v[MAXN], w[MAXN], dp[M];
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++)
{
int a, b, num;
scanf("%d%d%d",&a,&b,&num);
int k = 1;
while(k <= num)
{
cnt++;
v[cnt] = a * k;
w[cnt] = b * k;
num -= k;
k *= 2;
}
if(num > 0)
{
cnt++;
v[cnt] = a * num;
w[cnt] = b * num;
}
}
for(int i = 1;i <= cnt;i++)
{
for(int j = V;j >= v[i];j--)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
printf("%d\n",dp[V]);
return 0;
}
#include
#include
#include
using namespace std;
const int MAXN = 110;
int N, V;
int s[MAXN], w[MAXN][MAXN], v[MAXN][MAXN], dp[MAXN][MAXN];
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++)
{
scanf("%d",&s[i]);
for(int j = 1;j <= s[i];j++)
{
scanf("%d%d",&v[i][j],&w[i][j]);
}
}
for(int i = 1;i <= N;i++)
{
for(int j = 0;j <= V;j++)
{
dp[i][j] = dp[i-1][j];
for(int k = 1;k <= s[i];k++)
{
if(j >= v[i][k]) dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][k]]+w[i][k]);
}
}
}
printf("%d\n",dp[N][V]);
return 0;
}
#include
#include
#include
using namespace std;
const int MAXN = 110;
int N, V;
int v[MAXN][MAXN], w[MAXN][MAXN], s[MAXN], dp[MAXN];
int main()
{
scanf("%d%d",&N,&V);
for(int i = 1;i <= N;i++)
{
scanf("%d",&s[i]);
for(int j = 1;j <= s[i];j++)
{
scanf("%d%d",&v[i][j],&w[i][j]);
}
}
for(int i = 1;i <= N;i++)
{
for(int j = V;j >= 0;j--)
{
for(int k = 1; k <= s[i];k++)
{
if(j >= v[i][k]) dp[j] = max(dp[j],dp[j-v[i][k]]+w[i][k]);
}
}
}
printf("%d\n",dp[V]);
return 0;
}
898. 数字三角形 - AcWing题库
#include
#include
#include
using namespace std;
const int MAXN = 510;
int a[MAXN][MAXN], dp[MAXN][MAXN];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= i;j++)
{
scanf("%d",&a[i][j]);
}
}
dp[1][1] = a[1][1];
for(int i = 2;i <= n;i++)
{
for(int j = 1;j <= i;j++)
{
if(j == 1) dp[i][j] = dp[i-1][j] + a[i][j];
else if(j == i) dp[i][j] = dp[i-1][j-1] + a[i][j];
else dp[i][j] = max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
}
}
int ans = dp[n][1];
for(int i = 1;i <= n;i++) ans = max(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}
895. 最长上升子序列 - AcWing题库
#include
#include
#include
using namespace std;
const int MAXN = 1010;
int n;
int a[MAXN], dp[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= n;i++)
{
dp[i] = 1;
for(int j = 1;j < i;j++)
{
if(a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1);
}
}
int ans = dp[n];
for(int i = 1;i <= n;i++) ans = max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
O ( log n ) O(\log n) O(logn)优化
896. 最长上升子序列 II - AcWing题库
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
int a[MAXN], b[MAXN], n, id;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
b[id++] = a[1];
for(int i = 2;i <= n;i++) // 遍历每个数
{
if(a[i] <= b[id-1])
{
int pos = lower_bound(b,b+id,a[i])-b; // 二分找到第一个大于等于它的数进行替换
b[pos] = a[i];
}
else b[id++] = a[i];
}
printf("%d\n",id);
return 0;
}
897. 最长公共子序列 - AcWing题库
#include
#include
#include
#include
using namespace std;
int n, m;
const int MAXN = 1005;
int dp[MAXN][MAXN];
int main()
{
scanf("%d%d",&n,&m);
char s[MAXN], t[MAXN];
cin >> s+1 >> t+1;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
int ans = dp[n][m];
printf("%d\n",ans);
return 0;
}
902. 最短编辑距离 - AcWing题库
#include
#include
#include
#include
using namespace std;
const int MAXN = 1005;
int dp[MAXN][MAXN];
char s[MAXN], t[MAXN];
int n, m;
int min_3(int a, int b, int c)
{
int tmp = min(a, b);
return min(c,tmp);
}
int main()
{
scanf("%d",&n);
cin >> s+1;
scanf("%d",&m);
cin >> t+1;
// 注意初始化
for(int i = 1;i <= n;i++) dp[i][0] = i;
for(int j = 1;j <= m;j++) dp[0][j] = j;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1];
else dp[i][j] = min_3(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1;
}
}
int ans = dp[n][m];
printf("%d\n",ans);
return 0;
}
282. 石子合并 - AcWing题库
保证每次区间之内所用到的数都是之前计算过的。所以首先枚举区间长度。
#include
#include
#include
#include
using namespace std;
const int MAXN = 1010;
int dp[MAXN][MAXN], a[MAXN], sum[MAXN];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
}
for(int i = 1;i <= n;i++) dp[i][i] = 0;
for(int len = 2;len <= n;len++)
{
for(int i = 1;i + len - 1 <= n;i++)
{
int l = i, r = i+len-1;
dp[l][r] = dp[l][l]+dp[l+1][r]+sum[r]-sum[l-1];
for(int k = l;k < r;k++)
{
dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1]);
}
}
}
int ans = dp[1][n];
printf("%d\n",ans);
return 0;
}
900. 整数划分 - AcWing题库
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int MAXN = 1005;
int n;
ll dp[MAXN][MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) dp[n][1] = 1;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(j == i) dp[i][j] = (1 + dp[i][j-1]) % MOD;
else if(i < j) dp[i][j] = dp[i][i];
else dp[i][j] = (dp[i-j][j] + dp[i][j-1]) % MOD;
}
}
printf("%d\n",dp[n][n]);
return 0;
}
分情况讨论
[a,b] 0~9出现的次数
count(n,x) 表示[1,n]中x出现的次数
count(b,x)-count(a-1,x)
// 分情况讨论
291. 蒙德里安的梦想 - AcWing题库
当所有横向小方格都放完了,纵向小方格只有一种摆法。
故所有的摆放方式都和横向摆放小方格的摆放方式一样。
f[i][j]
表示第i
列是否有从前一列伸出的横向小方格。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll dp[12][3000];
bool vis[3000];
int main()
{
while(true)
{
int n, m;
scanf("%d%d",&n,&m);
if(n == 0 && m == 0) break;
memset(dp,0,sizeof(dp));
for(int i = 0;i < 1 << n;i++)
{
vis[i] = true;
int cnt = 0;
for(int j = 0;j < n;j++)
{
if((i >> j) & 1) // 注意运算顺序
{
if(cnt % 2 == 1) vis[i] = false;
}
else cnt++;
}
if(cnt % 2 == 1) vis[i] = false;
}
dp[0][0] = 1;
for(int i = 1;i <= m;i++)
{
for(int j = 0;j < (1<
91. 最短Hamilton路径 - AcWing题库
#include
#include
#include
#include
using namespace std;
const int MAXN = 1 << 20;
const int INF = 0x3f3f3f3f;
int w[21][21], dp[MAXN][21];
int n;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
for(int j = 0;j < n;j++)
{
scanf("%d",&w[i][j]);
}
}
memset(dp,INF,sizeof(dp));
dp[1][0] = 0;
for(int i = 0;i < (1<> j) & 1)
{
for(int k = 0;k < n;k++)
{
if((i >> k) & 1)
dp[i][j] = min(dp[i][j],dp[i-(1 << j)][k] + w[k][j]);
}
}
}
}
int ans = dp[(1 << n) - 1][n-1];
printf("%d\n",ans);
return 0;
}
// u的子树为s1,s2
f[u,0] = f[u,0] + max(f[s1,0],f[s1,1]) + max(f[s2,0],f[s2,1])
f[u,1] = f[u,1] + f[s1,0] + f[s2,0]
285. 没有上司的舞会 - AcWing题库
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 6010;
int n, id, head[MAXN], dp[MAXN][2], happy[MAXN];
struct Edge{
int to, next;
}edge[MAXN];
bool has_father[MAXN];
void addedge(int a, int b)
{
edge[id].to = b;
edge[id].next = head[a];
head[a] = id++;
return;
}
void dfs(int u)
{
dp[u][1] = happy[u];
for(int i = head[u];i != -1;i = edge[i].next)
{
int p = edge[i].to;
dfs(p);
dp[u][1] += dp[p][0];
dp[u][0] += max(dp[p][0],dp[p][1]);
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&happy[i]);
for(int i = 1;i <= n-1;i++)
{
int a, b;
scanf("%d%d",&a,&b);
addedge(b,a);
has_father[a] = true;
}
int root = 1;
while(has_father[root])
{
root++;
}
dfs(root);
int ans = max(dp[root][0],dp[root][1]);
printf("%d\n",ans);
return 0;
}
901. 滑雪 - AcWing题库
#include
#include
#include
#include
using namespace std;
const int MAXN = 305;
int n, m;
int dp[MAXN][MAXN], w[MAXN][MAXN];
int dx[4] = {0,1,0,-1}, dy[4] = {1,0,-1,0};
int dfs(int x, int y)
{
if(dp[x][y] != -1) return dp[x][y];
dp[x][y] = 1;
for(int i = 0;i < 4;i++)
{
int u = x + dx[i], v = y + dy[i];
if(u >= 1 && u <= n && v >= 1 && v <= m && w[u][v] < w[x][y])
{
dp[x][y] = max(dp[x][y],1+dfs(u,v));
}
}
return dp[x][y];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
scanf("%d",&w[i][j]);
}
}
int ans = 1;
memset(dp,-1,sizeof(dp));
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
ans = max(ans,dfs(i,j));
}
}
printf("%d\n",ans);
return 0;
}
905. 区间选点 - AcWing题库
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
int n;
struct node{ // 按照右端点进行排序
int l, r;
bool operator<(const node &a)
{
if(r == a.r) return l < a.l;
return r < a.r;
}
}Node[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d%d",&Node[i].l,&Node[i].r);
}
sort(Node+1,Node+n+1);
int cnt = 0;
int r;
for(int i = 1;i <= n;i++)
{
if(!cnt || Node[i].l > r) {
r = Node[i].r;
cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
908. 最大不相交区间数量 - AcWing题库
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
// 按照右端点排序
struct node{
int l, r;
bool operator<(const node &a)
{
if(r == a.r) return l < a.l;
return r < a.r;
}
}Node[MAXN];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d%d",&Node[i].l,&Node[i].r);
sort(Node+1,Node+1+n);
int cnt = 0, r;
for(int i = 1;i <= n;i++)
{
if(!cnt || Node[i].l > r)
{
r = Node[i].r;
cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
906. 区间分组 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
struct node{
int l, r;
bool operator<(const node &a)
{
if(l == a.l) return r < a.r;
return l < a.l;
}
}Node[MAXN];
int n;
priority_queue,greater>pq;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d%d",&Node[i].l,&Node[i].r);
}
sort(Node,Node+n);
int cnt = 0;
for(int i = 0;i < n;i++)
{
if(!cnt || pq.top() >= Node[i].l)
{
cnt++;
pq.push(Node[i].r);
}
else {
pq.pop();
pq.push(Node[i].r);
}
}
printf("%d\n",cnt);
return 0;
}
把所有开始时间和结束时间排序,遇到开始时间就把需要的教室加1,遇到结束时间就把需要的教室减1,在一系列需要的教室个数变化的过程中,峰值就是多同时进行的活动数,也是我们至少需要的教室数。
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+10;
int n;
int b[2*MAXN], idx;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
int l, r;
scanf("%d%d",&l,&r);
b[idx++] = 2*l; // 进行适当的奇偶变换
b[idx++] = 2*r+1; // 偶数为了避免重复加上一
}
sort(b,b+idx);
int t = 0, ans = 0;
for(int i = 0;i < idx;i++)
{
if(b[i] % 2 == 0) t++;
else t--;
ans = max(ans,t);
}
printf("%d\n",ans);
return 0;
}
AcWing 907. 区间覆盖 - AcWing
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iskX8AxW-1629167397792)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210706105409443.png)]
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
struct node{
int l, r;
bool operator<(const node &a) // 按照左端点排序
{
return l < a.l;
}
}Node[MAXN];
int n, le, ri;
int main()
{
bool flag = true;
scanf("%d%d",&le,&ri);
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d%d",&Node[i].l,&Node[i].r);
sort(Node,Node+n);
int start = le, tmp = le, cnt = 0;
for(int i = 0;i < n;)
{
if(start >= ri) break; // 超过区间退出
while(Node[i].l <= start && i < n)
{
tmp = max(tmp,Node[i].r); // 固定左端点,找最大的右端点
i++;
}
cnt++;
if(tmp == start) {
flag = false;
break;
}
start = tmp;
}
if(start < ri) flag = false; // 未到区间长度记为false
if(flag) printf("%d\n",cnt);
else printf("-1\n");
return 0;
}
148. 合并果子 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 10010;
int n;
int a[MAXN];
priority_queue,greater>pq;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++) {
int x;
scanf("%d",&x);
pq.push(x);
}
int ans = 0;
for(int i = 0;i < n-1;i++)
{
int u = pq.top();
pq.pop();
int v = pq.top();
pq.pop();
ans += u+v;
pq.push(u+v);
}
printf("%d\n",ans);
return 0;
}
活动 - AcWing
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int n;
const int MAXN = 100010;
int a[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d",&a[i]);
sort(a,a+n); // 按照打水短的先打水
ll ans = 0, sum = 0;
for(int i = 0;i < n;i++)
{
ans += sum;
sum += a[i];
}
printf("%lld\n",ans);
}
104. 货仓选址 - AcWing题库
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 100010;
int a[MAXN];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
int mid = a[n/2+1]; // 取中间的数作为建站点
ll ans = 0;
for(int i = 1;i <= n;i++) ans += abs(mid-a[i]);
printf("%d\n",ans);
return 0;
}
那么我们要求的就是找出一种牛的排列方式,令 m a x ( w 1 + ⋅ ⋅ ⋅ + w i − 1 − s i ) max(w_1+⋅⋅⋅+w_{i−1}−s_i) max(w1+⋅⋅⋅+wi−1−si)最小,记这个值为val。
为了求排序的方案,可以交换i,i+1牛的位置,看看满足什么等价条件,就可以使得交换之后val不比之前大。
由于s, w都是正数, w i − s i + 1 > − s i + 1 w_i−s_{i+1}>−s_{i+1} wi−si+1>−si+1 , w i + 1 − s i > − s i w_{i+1}−s_i>−s_i wi+1−si>−si
比较 w i − s i + 1 w_i−s_{i+1} wi−si+1 与 w i + 1 − s i w_{i+1}−s_{i} wi+1−si即可
所以得到做法: 按每头牛的 w + s 进行排序, 当存在逆序时就进行交换(即升序排序),
然后根据题意算出每头牛的危险值记录其中的最大值即可
125. 耍杂技的牛 - AcWing题库
#include
#include
#include
using namespace std;
typedef long long ll;
int n;
const int MAXN = 50050;
ll sum, ans;
struct Cow{
int s, w;
bool operator<(const Cow &a)
{
return s+w < a.s+a.w;
}
}cow[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d%d",&cow[i].w,&cow[i].s);
}
sort(cow,cow+n);
ans -= cow[0].s;
sum = cow[0].w;
for(int i = 1;i < n;i++)
{
if(sum - cow[i].s > ans) ans = sum - cow[i].s;
sum += cow[i].w;
}
printf("%lld\n",ans);
return 0;
}
一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 1 0 7 ∼ 1 0 8 10^7∼10^8 107∼108 为最佳。
下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0yz0FNM-1629167397793)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210706175918298.png)]
ode[i].l,&Node[i].r);
}
sort(Node,Node+n);
int cnt = 0;
for(int i = 0;i < n;i++)
{
if(!cnt || pq.top() >= Node[i].l)
{
cnt++;
pq.push(Node[i].r);
}
else {
pq.pop();
pq.push(Node[i].r);
}
}
printf("%d\n",cnt);
return 0;
}
把所有开始时间和结束时间排序,遇到开始时间就把需要的教室加1,遇到结束时间就把需要的教室减1,在一系列需要的教室个数变化的过程中,峰值就是多同时进行的活动数,也是我们至少需要的教室数。
```c++
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+10;
int n;
int b[2*MAXN], idx;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
int l, r;
scanf("%d%d",&l,&r);
b[idx++] = 2*l; // 进行适当的奇偶变换
b[idx++] = 2*r+1; // 偶数为了避免重复加上一
}
sort(b,b+idx);
int t = 0, ans = 0;
for(int i = 0;i < idx;i++)
{
if(b[i] % 2 == 0) t++;
else t--;
ans = max(ans,t);
}
printf("%d\n",ans);
return 0;
}
AcWing 907. 区间覆盖 - AcWing
[外链图片转存中…(img-iskX8AxW-1629167397792)]
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5+50;
struct node{
int l, r;
bool operator<(const node &a) // 按照左端点排序
{
return l < a.l;
}
}Node[MAXN];
int n, le, ri;
int main()
{
bool flag = true;
scanf("%d%d",&le,&ri);
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d%d",&Node[i].l,&Node[i].r);
sort(Node,Node+n);
int start = le, tmp = le, cnt = 0;
for(int i = 0;i < n;)
{
if(start >= ri) break; // 超过区间退出
while(Node[i].l <= start && i < n)
{
tmp = max(tmp,Node[i].r); // 固定左端点,找最大的右端点
i++;
}
cnt++;
if(tmp == start) {
flag = false;
break;
}
start = tmp;
}
if(start < ri) flag = false; // 未到区间长度记为false
if(flag) printf("%d\n",cnt);
else printf("-1\n");
return 0;
}
148. 合并果子 - AcWing题库
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 10010;
int n;
int a[MAXN];
priority_queue,greater>pq;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++) {
int x;
scanf("%d",&x);
pq.push(x);
}
int ans = 0;
for(int i = 0;i < n-1;i++)
{
int u = pq.top();
pq.pop();
int v = pq.top();
pq.pop();
ans += u+v;
pq.push(u+v);
}
printf("%d\n",ans);
return 0;
}
活动 - AcWing
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int n;
const int MAXN = 100010;
int a[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++) scanf("%d",&a[i]);
sort(a,a+n); // 按照打水短的先打水
ll ans = 0, sum = 0;
for(int i = 0;i < n;i++)
{
ans += sum;
sum += a[i];
}
printf("%lld\n",ans);
}
104. 货仓选址 - AcWing题库
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 100010;
int a[MAXN];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
int mid = a[n/2+1]; // 取中间的数作为建站点
ll ans = 0;
for(int i = 1;i <= n;i++) ans += abs(mid-a[i]);
printf("%d\n",ans);
return 0;
}
那么我们要求的就是找出一种牛的排列方式,令 m a x ( w 1 + ⋅ ⋅ ⋅ + w i − 1 − s i ) max(w_1+⋅⋅⋅+w_{i−1}−s_i) max(w1+⋅⋅⋅+wi−1−si)最小,记这个值为val。
为了求排序的方案,可以交换i,i+1牛的位置,看看满足什么等价条件,就可以使得交换之后val不比之前大。
由于s, w都是正数, w i − s i + 1 > − s i + 1 w_i−s_{i+1}>−s_{i+1} wi−si+1>−si+1 , w i + 1 − s i > − s i w_{i+1}−s_i>−s_i wi+1−si>−si
比较 w i − s i + 1 w_i−s_{i+1} wi−si+1 与 w i + 1 − s i w_{i+1}−s_{i} wi+1−si即可
所以得到做法: 按每头牛的 w + s 进行排序, 当存在逆序时就进行交换(即升序排序),
然后根据题意算出每头牛的危险值记录其中的最大值即可
125. 耍杂技的牛 - AcWing题库
#include
#include
#include
using namespace std;
typedef long long ll;
int n;
const int MAXN = 50050;
ll sum, ans;
struct Cow{
int s, w;
bool operator<(const Cow &a)
{
return s+w < a.s+a.w;
}
}cow[MAXN];
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d%d",&cow[i].w,&cow[i].s);
}
sort(cow,cow+n);
ans -= cow[0].s;
sum = cow[0].w;
for(int i = 1;i < n;i++)
{
if(sum - cow[i].s > ans) ans = sum - cow[i].s;
sum += cow[i].w;
}
printf("%lld\n",ans);
return 0;
}
一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 1 0 7 ∼ 1 0 8 10^7∼10^8 107∼108 为最佳。
下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:
[外链图片转存中…(img-Y0yz0FNM-1629167397793)]