##背景
辣鸡的人总要想法自救,便产生了寒假学些新算法、在Bzoj刷些题的想法。一来为明年省赛做准备…压力不小;二来寒假也可以有些事情做。
##PS
1.不定时更新做题的思路和吐槽
2.按照hzw刷题顺序训练,具体依照BZOJ题表
3.希望寒假能够至少刷够100道题目吧,在此立个FLAG,希望别被青岛的妖风吹跑
##Problem & Solution
BZOJ 1003
######题意
给定一个图的具体信息,但是在m天内,每一天都可能有一些点不可到达,现需要规划m天内每天1到 n的最短路线,路线在相邻的两天变更会产生固定代价。
Solution
路线的规划最终方案为一个时间段用一个路径,若干时间段最后共同构成1-m天,每个时间段内使用的路径一定是可用点组成的最短路径。因此我们枚举时间段的起点和终点,然后依次进行spfa,然后用这些数值去做一个线性的dp
复杂度:O((nm)^2)
/**************************************************************
Problem: 1003
User: YuHsin
Language: C++
Result: Accepted
Time:64 ms
Memory:1388 kb
****************************************************************/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define inf 100000000
const int N = 300;
int n, m, k, e;
int f[N];
bool vis[N][N], point[N];
struct node {
int y, d;
};
int dis[N];
bool passed[N];
vector<node> edge[N];
int spfa(int start, int endd) {
for(int i = 1; i <= m; i++) {
point[i] = true;
for(int j = start; j <= endd; j++) {
if (vis[i][j]) point[i] = false;
}
}
if (!point[1] || !point[m]) return inf;
queue<int> q;
for(int i = 1; i <= m; i++) {
passed[i] = false;
dis[i] = inf;
}
dis[1] = 0; passed[1] = true;
q.push(1);
while(!q.empty()) {
int x = q.front();
q.pop();
passed[x] = false;
for(int j = 0; j < edge[x].size(); j++) {
node e = edge[x][j];
if (!point[e.y]) continue;
if (dis[e.y] > dis[x] + e.d) {
dis[e.y] = dis[x] + e.d;
if (!passed[e.y]) {
passed[e.y] = true;
q.push(e.y);
}
}
}
}
return dis[m];
}
int main()
{
scanf("%d%d%d%d", &n, &m, &k, &e);
for(int i = 1; i <= e; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
edge[x].push_back((node){y, z});
edge[y].push_back((node){x, z});
}
int tot;
scanf("%d", &tot);
for(int i = 1; i <= tot; i++) {
int p, l, r;
scanf("%d%d%d", &p, &l, &r);
for(int j = l; j <= r; j++) vis[p][j] = true;
}
for(int i = 1; i <= n; i++) f[i] = inf;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < i; j++) {
int res = spfa(j + 1, i);
if (res < inf) f[i] = min(f[i], k + f[j] + (i - j) * res);
}
}
printf("%d", f[n] - k);
return 0;
}
BZOJ 1192
题意
给定正整数n,将其分成尽可能少的若干正整数之和,任意两个正整数均不可以相同,先询问整数个数
Solution
按照二次幂一直分就可以
BZOJ 1303
题意
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b
Solution
一个连续序列的中位数是b,那么这个序列中比b大的数和比b小的数字一定个数相同,因此我们在这个排列中找到b的位置,设为pos。我们用res代表一个区间比b大的数与比b小的数数字个数之差(可能为负值),用cnt1[i]代表在以pos作为右端点的区间中,区间res值为i的区间个数;cnt2[i]代表以pos作为左端点的取件中,区间res值为-i的区间个数,那么我们可以O(n)的维护cnt1和cnt2
最终ans=∑(cnt1[i]*cnt2[i])
#include<cstdio>
#include<iostream>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 210000;
int a[N];
int cnt1[N], cnt2[N];
int n, m;
int main()
{
int base = 100005, pos;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", a + i);
if (a[i] == m) pos = i;
}
int tot = base;
for(int i = pos; i > 0; i--) {
if (a[i] < m) tot--;
else if (a[i] > m) tot++;
cnt1[tot]++;
}
tot = base;
for(int i = pos; i <= n; i++) {
if (a[i] < m) tot++;
else if (a[i] > m) tot--;
cnt2[tot]++;
}
ll ans = 0;
for(int i = base - 100000; i <= base + 100000; i++) ans += 1LL * cnt1[i] * cnt2[i];
cout << ans;
return 0;
}
BZOJ 1191
Solution
每道题对应两个锦囊,相当于一个男孩对于两个女孩,直接二分图匹配就可以
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 2003;
int n, m;
vector<int> edge[N];
bool used[N];
int girl[N];
bool find(int x) {
for(int i = 0; i < edge[x].size();i++) {
int y = edge[x][i];
if (used[y]) continue;
used[y] = true;
if (girl[y] == 0 || find(girl[y])) {
girl[y] = x;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
edge[i].push_back(++x);
edge[i].push_back(++y);
}
for(int i = 1; i <= m; i++) {
for(int i = 1; i <= n; i++) used[i] = false;
if (!find(i)) {
printf("%d", i - 1);
return 0;
}
}
printf("%d", m);
return 0;
}
BZOJ 1059
题意
给定01矩阵,现可以进行行交换和列交换操作,判断是否可以通过进行一些操作,使得矩阵一条对角线的元素全为1
Solution
很nb的一道题目
我们先考虑题目的简化版:假设操作只有行交换。对于同一行的元素,不管如何进行行操作,他们行坐标都是相同的,即这些元素中只有一个可以作为对角线的元素,假设这一行i中只有第三列和第五列为1,那么这一行可以通过行交换将a[3][3] 变成1, 或者a[5][5] 变成1,即第i行可以匹配的列为3和5。按照这个思想,我们按照行和列做二分图匹配,最终判断是否存在完美匹配即可
现在考虑列交换,实际上列交换就是相当于把之前的二分图右面的点交换位置而已,并不影响最终答案的判断
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 503;
int n, m;
bool used[N];
int girl[N];
bool a[N][N];
bool find(int x) {
for(int j = 1; j <= n; j++) {
if (!a[x][j] || used[j]) continue;
used[j] = true;
if (girl[j] == 0 || find(girl[j])) {
girl[j] = x;
return 1;
}
}
return 0;
}
int main()
{
int T;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) girl[i] = 0;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) used[j] = false;
if (find(i)) ans++;
}
if (ans == n) puts("Yes");
else puts("No");
}
return 0;
}
BZOJ 1202
题意
给定m个区间和,判断是否产生矛盾
Solution
矛盾的题目很容易想到并查集
我的思路很奇葩,首先将这些区间按照右端点排序,使用bool 的f[i][j]代表是否已知[j, i]的区间和,枚举区间[l,r]时,先判断是否原本就已知该区间的和,判断是否矛盾,然后将f[r][l]标记上,注意我们在已知[l,r]的区间和后,实际上f[l-1]为true的那些左端点,也可以被更新到f[r]中,更新时不要忘记判断矛盾
复杂度O(Mlogn+mn)
正常思路:带权并查集
每个集合代表一个连续的区间,f[i]代表所述集合,g[i]代表从i节点到所在树root的区间和,合并的时候维护即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200;
int value[N][N];
int n, m;
int inf = 210000000;
struct node {
int l, r, w;
} a[3000];
bool cmp(node a, node b) {
if (a.r == b.r) return a.l < b.l;
return a.r < b.r;
}
bool Cal() {
for(int i = 1; i <= m; i++) {
int l = a[i].l, r = a[i].r, w = a[i].w;
if (value[r][l] != inf && value[r][l] != w) return false;
value[r][l] = w;
for(int j = 1; j <= l - 1; j++) if (value[l - 1][j] != inf) {
if (value[r][j] != inf && value[r][j] != value[l - 1][j] + value[r][l]) return false;
value[r][j] = value[l - 1][j] + value[r][l];
}
}
return true;
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) for(int j = 1; j <= i; j++) value[i][j] = inf;
for(int i = 1; i <= m; i++) scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].w);
sort(a + 1, a + m + 1, cmp);
if (Cal()) puts("true"); else puts("false");
}
return 0;
}
// 并查集
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<algorithm>
using namespace std;
const int N = 200;
int n, m;
int f[N], g[N];
int find(int x) {
if (f[x] == x) return x;
int t = find(f[x]);
g[x] += g[f[x]];
return f[x] = t;
}
bool flag;
bool unio(int x, int y, int z) {
int xx = find(x), yy = find(y);
if (xx != yy) {
g[xx] = -g[x] + z + g[y];
f[xx] = yy;
}
else if (g[x] - g[y] != z) flag = false;
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
flag = true;
for(int i = 0; i <= n; i++) f[i] = i, g[i] = 0;
for(int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
unio(x - 1, y, z);
}
if (flag) puts("true"); else puts("false");
}
return 0;
}
BZOJ 3223
题意
平衡树的裸题
Solution
splay模板即可
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 1000007
int ch[N][2], f[N], size[N], cnt[N], key[N];
int sz, root;
inline void clear(int x) {
ch[x][0] = ch[x][1] = f[x] = size[x] = cnt[x] = key[x] = 0;
}
inline bool get(int x) {
return ch[f[x]][1] == x;
}
inline void update(int x) {
if (x) {
size[x] = cnt[x];
if (ch[x][0]) size[x] += size[ch[x][0]];
if (ch[x][1]) size[x] += size[ch[x][1]];
}
}
inline void rotate(int x) {
int old = f[x], oldf = f[old], whichx = get(x);
ch[old][whichx] = ch[x][whichx^1]; f[ch[old][whichx]] = old;
ch[x][whichx^1] = old; f[old] = x;
f[x] = oldf;
if (f[x]) ch[oldf][ch[oldf][1] == old] = x;
update(old); update(x);
}
inline void splay(int x) {
for(int fa; (fa = f[x]); rotate(x))
if (f[fa]) rotate((get(x) == get(fa)) ? fa : x);
root = x;
}
inline void insert(int x) {
if (root == 0) {
sz++; ch[sz][0] = ch[sz][1] = f[sz] = 0;
size[sz] = cnt[sz] = 1;
key[sz] = x; root = sz; return;
}
int now = root, fa = 0;
while(true) {
if (x == key[now]) {
cnt[now]++; update(now); update(fa); splay(now); break;
}
fa = now;
now = ch[now][x > key[now]];
if (now == 0) {
sz++; ch[sz][0] = ch[sz][1] = 0;
f[sz] = fa; key[sz] = x;
size[sz] = cnt[sz] = 1;
ch[fa][key[fa]<x] = sz;
update(fa); splay(sz); break;
}
}
}
inline int find(int x) {
int now = root, ans = 0;
while(true) {
if (x < key[now]) now = ch[now][0];
else {
ans += (ch[now][0]?size[ch[now][0]]:0);
if (x == key[now]) { splay(now); return ans + 1; }
ans += cnt[now];
now = ch[now][1];
}
}
}
inline int findx(int x) {
int now = root;
while(true) {
if (ch[now][0] && x <= size[ch[now][0]]) now = ch[now][0];
else {
int temp = cnt[now] + (ch[now][0]?size[ch[now][0]]:0);
if (x <= temp) return key[now];
x -= temp; now = ch[now][1];
}
}
}
inline int pre() {
int now = ch[root][0];
while(ch[now][1]) now = ch[now][1];
return now;
}
inline int nxt() {
int now = ch[root][1];
while(ch[now][0]) now = ch[now][0];
return now;
}
inline void del(int x) {
int whatever = find(x);
if (cnt[root] > 1) {
cnt[root]--; update(root); return;
}
if (!ch[root][0] && !ch[root][1]) { clear(root); root = 0; return; }
if (!ch[root][0]) {
int oldroot = root; root = ch[root][1]; f[root] = 0;
clear(oldroot); return;
}
if (!ch[root][1]) {
int oldroot = root; root = ch[root][0]; f[root] = 0;
clear(oldroot); return ;
}
int leftbig = pre(), oldroot = root;
splay(leftbig);
f[ch[oldroot][1]] = root;
ch[root][1] = ch[oldroot][1];
clear(oldroot);
update(root); return;
}
int main(){
int n,opt,x;
scanf("%d",&n);
for (int i=1;i<=n;++i){
scanf("%d%d",&opt,&x);
switch(opt){
case 1: insert(x); break;
case 2: del(x); break;
case 3: printf("%d\n",find(x)); break;
case 4: printf("%d\n",findx(x)); break;
case 5: insert(x); printf("%d\n",key[pre()]); del(x); break;
case 6: insert(x); printf("%d\n",key[nxt()]); del(x); break;
}
}
return 0;
}
2018.03.04
队伍发生变更,原来的一名队员换成了学姐,寒假完成了预期的46%…
##BZOJ 1051
[Solution]
我们将“A认为B是受欢迎的”处理为“A->B连边”,然后我们对此有向图进行Tarjan缩点,如果存在多于一个出度为0的强连通分量,那么无解;否则,出度为零的强联通分量点的个数即为答案。
##BZOJ 1588
[Solution]
可以直接用splay裸着做,或者通过set实现;通过set内部的lower_bound实现即可
##BZOJ 1208
[Solution]
splay模板题
##BZOJ 3224
[Solution]
splay模板题
##BZOJ 1084
[Solution]
注意m很小,因此我们用dp[i][j]代表第一列的前i行和第二列的前j行的最优解
dp[ i ] [ j ] = max{ dp[i][j-1], dp[i-1][j],dp[k][j] + sum[1][k+1]~sum[1][i] (i > j, 0 <=k < i),
dp[k][k] + sum[1][k+1 i]+sum[2][k+1i](i=j, 0 < k < i ),
dp[i][k] + sum[2][k+1~j] (i < j, 0 <=k < j) }
复杂度:O(N* N* N *K)
##BZOJ 1491
[Solution]
我们令dp[i] [j] 代表i到j最短路径数,可以通过n边Dijlstra处理出来,然后通过N^3的复杂度处理出答案即可
##BZOJ 1295
把障碍数作为距离跑spfa,将dis<=T的点的欧几里得距离update到ans中
##bzoj 1085
暴力的时候增加一个估价函数,即使用最优性剪枝