写日报真的是很好的督促学习的方式
暑假以来比较浪,从今天开始改变自己
周一
Concatenation(字符串比较)
暴力排序竟然可以过……
显然难点在于前缀相同的情况,这个时候比较ab和ba即可
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int N = 2e6 + 10;
string a[N];
bool cmp(string x, string y)
{
int len = min(x.size(), y.size());
rep(i, 0, len)
if(x[i] != y[i])
return x[i] < y[i];
return x + y < y + x;
}
int main()
{
int n; scanf("%d", &n);
_for(i, 1, n) cin >> a[i];
sort(a + 1, a + n + 1, cmp);
_for(i, 1, n) cout << a[i];
puts("");
return 0;
}
Ancestor(前缀后缀+代码技巧)
比赛时分类讨论去了,其实是弄复杂了
一个区间去掉一个点,就剩下前缀和后缀了,于是我们可以预处理出前缀lca和后缀lca
因为同样的代码要写两份,所以写个结构体可以大大简化代码
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int N = 1e5 + 10;
int k[N], n, m;
struct Tree
{
int d[N], up[N][20], q[N], h[N], val[N];
vector<int> g[N];
void read()
{
_for(i, 1, n) scanf("%d", &val[i]);
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
}
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
up[u][0] = fa;
_for(j, 1, 19) up[u][j] = up[up[u][j - 1]][j - 1];
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
}
}
int lca(int u, int v)
{
if(!u || !v) return u + v;
if(d[u] < d[v]) swap(u, v);
for(int j = 19; j >= 0; j--)
if(d[up[u][j]] >= d[v])
u = up[u][j];
if(u == v) return u;
for(int j = 19; j >= 0; j--)
if(up[u][j] != up[v][j])
u = up[u][j], v = up[v][j];
return up[u][0];
}
void init()
{
dfs(1, 0);
q[1] = k[1];
_for(i, 2, m) q[i] = lca(q[i - 1], k[i]);
h[m] = k[m];
for(int i = m - 1; i >= 1; i--) h[i] = lca(h[i + 1], k[i]);
}
int cal(int x)
{
return val[lca(q[x - 1], h[x + 1])];
}
}A, B;
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, m) scanf("%d", &k[i]);
A.read(); A.init();
B.read(); B.init();
int ans = 0;
_for(i, 1, m)
if(A.cal(i) > B.cal(i))
ans++;
printf("%d\n", ans);
return 0;
}
Journey(0/1bfs)
边看作点,路口看作边建图,边的权值只有0和1,这时用0/1bfs即可,和bfs复杂度一样
注意一些细节,建图配id的时候,注意用unordered_map
0/1bfs和bfs的区别在于用双端队列,边权为0的点放前面,否则放后面
用松弛操作来更新,这样一个点最多入队两次。然后不需要vis数组
此外,第一次找到的点不一定是最优的,但第一次出队的点一定是最优的
其实0/1bfs就是一个特殊的dijsktra,只是说优先队列变成了双端队列,通过入队方式来保证单调性。
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
typedef long long ll;
const int N = 5e5 * 8 + 10;
unordered_map<ll, int> mp;
vector<pair<int, int>> g[N];
int d[N], vis[N], n, cnt;
int id(int x, int y)
{
ll t = 1LL * x * 1e6 + y;
if(!mp[t]) mp[t] = ++cnt;
return mp[t];
}
int solve(int st, int dest)
{
_for(i, 1, cnt) d[i] = 1e9;
d[st] = 0;
deque<int> q; //0/1bfs用双端队列
q.push_back(st);
while(!q.empty())
{
int u = q.front(); q.pop_front();
if(u == dest) return d[u]; //0/1bfs中,出队的时候是最小值,第一次遇到不一定是最小值
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(d[v] > d[u] + w) //松弛操作
{
d[v] = d[u] + w;
if(!w) q.push_front(v); //0放队首 1放队尾
else q.push_back(v);
}
}
}
return -1;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int c[5];
_for(j, 1, 4) scanf("%d", &c[j]);
_for(j, 1, 4)
{
if(!c[j]) continue;
g[id(c[j], i)].push_back({id(i, c[j]), 1});
int ii = j % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 0});
ii = (j + 1) % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
ii = (j + 2) % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
}
}
int s1, s2, t1, t2;
scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
int st = id(s1, s2), dest = id(t1, t2);
printf("%d\n", solve(st, dest));
return 0;
}
这题直接dijsktra也可以过
当然要注意,第一次入队不一定是最优的,第一次出队才是最优的。或者干脆算法跑完后再输出。
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
typedef long long ll;
const int N = 5e5 * 8 + 10;
unordered_map<ll, int> mp;
int d[N], vis[N], n, cnt;
struct node
{
int v, w;
bool operator < (const node& rhs) const
{
return w > rhs.w;
}
};
vector<node> g[N];
int id(int x, int y)
{
ll t = 1LL * x * 1e6 + y;
if(!mp[t]) mp[t] = ++cnt;
return mp[t];
}
int solve(int st, int dest)
{
_for(i, 1, cnt) d[i] = 1e9;
d[st] = 0;
priority_queue<node> q;
q.push({st, d[st]});
while(!q.empty())
{
node x = q.top(); q.pop();
int u = x.v;
if(u == dest) return d[u];
if(d[u] != x.w) continue;
for(auto x: g[u])
{
int v = x.v, w = x.w;
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
q.push({v, d[v]});
}
}
}
return -1;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int c[5];
_for(j, 1, 4) scanf("%d", &c[j]);
_for(j, 1, 4)
{
if(!c[j]) continue;
g[id(c[j], i)].push_back({id(i, c[j]), 1});
int ii = j % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 0});
ii = (j + 1) % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
ii = (j + 2) % 4 + 1;
if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
}
}
int s1, s2, t1, t2;
scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
int st = id(s1, s2), dest = id(t1, t2);
printf("%d\n", solve(st, dest));
return 0;
}
周二
UVA1723 Intervals(差分约束)
这是差分约束的裸题
差分约束就是有n个变量,m个不等式,然后求某个变量的最值
比如x1 - x2 >= 2
转化一下为x1 >= x2 + 2
这个式子类似于最短路里面的dis1 <= dis2 + w
如果换成最长路就有dis1 >= dis2 + w
因此差分约束就是建图然后跑最长路,对于这个式子就是2号点向1号点连一条长度为2的边
对于这道题而言,转化为前缀和的形式可以得到m个等式
但是注意还有一些隐藏条件,一个点要不有要不没有,所以1 >= di - di-1 >= 0
这样子建图完之后,还有一些细节
比如这道题从0开始,但是有i-1,于是我们可以把所有输入+1,就没有这个问题
然后,这样子其实只是求得各种不等式,如果变量要取值,必须有1个变量要取一个具体的值
于是我们设d[0] = 0, 也就是这个变量取了0,然后其他变量都能推出来了。
除了0以外其他变量要设为最小值,不要设为0
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int N = 5e4 + 10;
int d[N], vis[N], n, mx;
vector<pair<int, int>> g[N];
int spfa()
{
_for(i, 1, mx) d[i] = -1e9; //求最长路的时候 这个地方要反过来设为最小值,不能设为0
queue<int> q;
q.push(0);
vis[0] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
vis[u] = 0;
for(auto t: g[u])
{
int v = t.first, w = t.second;
if(d[v] < d[u] + w) //松弛操作注意不等号方向
{
d[v] = d[u] + w;
if(!vis[v])
{
vis[v] = 1;
q.push(v);
}
}
}
}
return d[mx];
}
int main()
{
int T, fi = 1; scanf("%d", &T);
while(T--)
{
if(fi) fi = 0;
else puts("");
mx = 0;
scanf("%d", &n);
_for(i, 1, n)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a++; b++;
g[a - 1].push_back({b, c});
mx = max(mx, b);
}
_for(i, 1, mx)
{
g[i - 1].push_back({i, 0});
g[i].push_back({i - 1, -1});
}
printf("%d\n", spfa());
_for(i, 0, mx) g[i].clear();
}
return 0;
}
Link with Level Editor I(bfs+省空间)
这题很容易想到建图bfs,但是比赛的时候被空间限制卡住了
怎么省呢?用时间换空间。如果按照正常的存图方式,就会炸空间,要换一种存图方式
用m个vector,每个vector存这个点在所有世界的边,每次查找用二分。对于一定会有的边,即原地不动的情况,这个时候不存到数组里面,而是搜索的时候特殊处理即可
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int N = 1e4 + 10;
const int M = 2e3 + 10;
vector<pair<int, int>> edge[N];
struct node { int c, u, step; };
bitset<M> vis[N];
int n, m;
int solve()
{
queue<node> q;
_for(i, 1, n)
{
q.push({i, 1, 1});
vis[i][1] = 1;
}
while(!q.empty())
{
node x = q.front(); q.pop();
int c = x.c, u = x.u, step = x.step;
int l = lower_bound(edge[u].begin(), edge[u].end(), make_pair(c, 0)) - edge[u].begin();
int r = upper_bound(edge[u].begin(), edge[u].end(), make_pair(c, (int)1e9)) - edge[u].begin();
rep(i, l, r)
{
int v = edge[u][i].second;
if(vis[c][v]) continue;
vis[c][v] = 1;
if(v == m) return step;
q.push({c + 1, v, step + 1});
}
if(c < n)
{
if(vis[c + 1][u]) continue;
vis[c + 1][u] = 1;
q.push({c + 1, u, step + 1});
}
}
return -1;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
int l; scanf("%d", &l);
while(l--)
{
int u, v;
scanf("%d%d", &u, &v);
edge[u].push_back({i, v});
}
}
printf("%d\n", solve());
return 0;
}
Link with Level Editor I(dp)
这题还有dp的做法,状态设计比较巧妙
设dp[i][j]表示从第k个世界1到达第i个世界j的最大k是多少
初始化dp[i][1] = i + 1
dp[i + 1][v] = max(dp[i][u])
用滚动数组省空间,注意初始化时不合法的状态要设置为最小值
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int M = 2e3 + 10;
int dp[2][M], n, m;
int main()
{
scanf("%d%d", &n, &m);
int ans = 1e9, t = 0;
_for(i, 1, m) dp[t][i] = -1e9;
_for(i, 1, n)
{
_for(j, 1, m) dp[t ^ 1][j] = dp[t][j]; //先赋值不动的情况
dp[t][1] = i;
int l; scanf("%d", &l);
while(l--)
{
int u, v;
scanf("%d%d", &u, &v);
dp[t ^ 1][v] = max(dp[t ^ 1][v], dp[t][u]);
if(v == m) ans = min(ans, i + 1 - dp[t ^ 1][v]);
}
t ^= 1;
}
printf("%d\n", ans == 1e9 ? -1 : ans);
return 0;
}
周四
Link with Monotonic Subsequence(Dilworth定理)
可以打表找规律
这题其实用到了一个定理,也就是一序列的最长上升子序列=最小划分的下降子序列。
右边的意思是最少可以把序列划分成多少个下降子序列。这个等式上升下降可以调换,这是一个定理,即Dilworh定理。
n<=lds * lis
也就是说最长下降子序列长度是lds,划分个数又是lis
所以可以得到max(lds, lis) >= 根号n上取整。
所以答案是这个,就可以按照这个答案的长度为一段来划分即可。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
int t = ceil(sqrt(n));
vector<int> ve;
for(int i = n - t + 1; ; i -= t)
{
_for(j, i, i + t - 1)
if(j >= 1)
ve.push_back(j);
if(i < 1) break;
}
for(int x: ve) printf("%d ", x);
puts("");
}
return 0;
}
Link with Arithmetic Progression(推公式)
其实就是线性回归,不记得就直接手推
写出出目标函数,然后求偏导为0
比赛中总是犯不该犯的错误,自信点,平时保持训练状态
#include<bits/stdc++.h>
#define _for(i, a, b) for(int i = a; i <= b; i++)
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
const int N = 1e5 + 10;
long double y[N], x[N], A, B, ans;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
long double a = 0, b = 0, c = 0, d = 0;
_for(i, 1, n)
{
scanf("%Lf", &y[i]);
x[i] = i;
a += x[i];
b += x[i] * x[i];
c += x[i] * y[i];
d += y[i];
}
B = (a * c - b * d) / (a * a - n * b);
A = (c - a * B) / b;
ans = 0;
_for(i, 1, n)
{
long double cur = y[i] - (A * x[i] + B);
ans += pow(cur, 2);
}
printf("%.10Lf\n", ans);
}
return 0;
}
周日
Jobs (Easy Version)(前缀和+二进制)
用类似三维前缀和的思想。
因为只有10个公司,这么小的数据可以想到二进制,用二进制优化。
#include <bits/stdc++.h>
#include <random>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 400 + 10;
const int mod = 998244353;
int a[N][N][N];
int n, q, seed;
int solve(int x, int y, int z)
{
int res = 0;
rep(i, 0, n)
res += (a[x][y][z] >> i) & 1;
return res;
}
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int main()
{
scanf("%d%d", &n, &q);
rep(i, 0, n)
{
int m; scanf("%d", &m);
while(m--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
a[x][y][z] |= 1 << i;
}
}
scanf("%d", &seed);
mt19937 rng(seed);
uniform_int_distribution<> u(1,400);
_for(i, 1, 400)
_for(j, 1, 400)
_for(k, 1, 400)
a[i][j][k] = a[i][j][k] | a[i - 1][j][k] | a[i][j - 1][k] | a[i][j][k - 1];
ll ans = 0;
int lastans=0;
for (int i=1;i<=q;i++)
{
int IQ=(u(rng)^lastans)%400+1; // The IQ of the i-th friend
int EQ=(u(rng)^lastans)%400+1; // The EQ of the i-th friend
int AQ=(u(rng)^lastans)%400+1; // The AQ of the i-th friend
lastans=solve(IQ,EQ,AQ); // The answer to the i-th friend
ans = (ans + lastans * binpow(seed, q - i) % mod) % mod;
}
printf("%lld\n", ans);
return 0;
}
Particle Arts(二进制/数据类型)
可以发现最后稳定的状态肯定是任意两个数的二进制为包含关系
那么将它们排序,就可以得到某一位是一堆1然后是一堆0
又因为这个过程中一位上1的个数是不变的,所以就可以推出最后的结果
那么求方差即可,不要用分数,把式子带进去。
我一开始留着平方没化简,然后爆long long了,后面用__int128过了
其实把平方拆开,可以约去一个n,然后用unsigned long long就不会爆了
unsigned long long版
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
const int N = 1e5 + 10;
int num[20], n;
ull a[N];
ull gcd(ull a, ull b) { return !b ? a : gcd(b, a % b); }
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
_for(j, 0, 16)
if(x & (1 << j))
num[j]++;
}
_for(i, 0, 16)
_for(j, 1, num[i])
a[j] |= (1 << i);
ull sum = 0, p = 0, q = 0;
_for(i, 1, n) sum += a[i], p += a[i] * a[i];
p = n * p - sum * sum;
q = 1LL * n * n;
ull g = gcd(p, q);
printf("%llu/%llu\n", p / g, q / g);
return 0;
}
__int128版
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int num[20], n;
__int128 a[N];
__int128 gcd(__int128 a, __int128 b)
{
return !b ? a : gcd(b, a % b);
}
void print(__int128 n)
{
if(n / 10) print(n / 10);
putchar(n % 10 + '0');
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
_for(j, 0, 16)
if(x & (1 << j))
num[j]++;
}
_for(i, 0, 16)
_for(j, 1, num[i])
a[j] |= (1 << i);
__int128 sum = 0, p = 0, q = 0;
_for(i, 1, n) sum += a[i];
_for(i, 1, n) p += (sum - n * a[i]) * (sum - n * a[i]);
q = 1LL * n * n * n;
__int128 g = gcd(p, q);
print(p / g);
putchar('/');
print(q / g);
return 0;
}