T1 周
该题目过水已隐藏
$ O(2^{15}) $ 暴力dfs就好了。
直接扔代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n;
const int MAXN = 20;
int a[MAXN], b[MAXN], c[MAXN], d[MAXN];
int ans = 0;
void dfs(int day, int oi, int whk) {
if (day > n) {
ans = max(ans, oi * whk);
return;
}
dfs(day + 1, max(oi - b[day], 0ll), whk + a[day]);
dfs(day + 1, oi + c[day], max(whk - d[day], 0ll));
}
signed main() {
scanf("%lld", &n);
for (register int i = 1; i <= n; ++i) scanf("%lld%lld%lld%lld", &a[i], &b[i], &c[i], &d[i]);
dfs(0, 0, 0);
printf("%lld
", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
T2任
在画板上有一片黑白相间的矩形区域满足这样的性质:如果认为相同颜色的方块可以在上下左右四个方向连通,那么任意两个黑色方块要么不连通,要么连通但之间只有一条简单路径(不重复经过同一个格子的路径)。
这个矩形区域有(N)行(M)列, 从上到下依次为第(1, 2, 3... N-1, N)行, 从左到右依次为第(1, 2, 3... M-1, M)列。
每次郭神会询问这片矩形区域内的一个子矩形。在只考虑这个子矩形内的像素时(即从子矩形内部不能和子矩形之外的像素相连通),问这个子矩形内的黑色方块组成了多少连通块。
保证任意两个黑色像素之间最多只有一条简单路径。
咋做
注意审题
注意审题
注意审题
题里说了联通的部分是一棵树。所以整张图是一个森林,那么题目的询问转化为了这个范围内有多少颗树。树有什么性质呢?众所周知树有(n)个点,(n-1)条边。当我们已知点数和边数,树的棵数就是点数减边数。
树的点数、边数可以(O(nm))预处理,再利用二维前缀和便可以做到(O(1))查询。整体复杂度(O(nm + q))。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 5;
char mp[MAXN][MAXN];
bool v[MAXN][MAXN];
int sum1[MAXN][MAXN], sum2[MAXN][MAXN], sum3[MAXN][MAXN];
int step[][2] = { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } };
int n, m, q;
inline void bfs() {
queue<pair<int, int>> q;
for (register int i = 1; i <= n; ++i)
for (register int j = 1; j <= m; ++j) {
if (mp[i][j] == '1' && !v[i][j]) {
q.push(make_pair(i, j));
while (q.size()) {
int x = q.front().first, y = q.front().second;
int xi, yi;
v[x][y] = true;
q.pop();
for (register int i = 0; i <= 0; ++i) {
xi = x;
yi = y + step[i][1];
if (mp[xi][yi] == '1' && !v[xi][yi] && xi >= 1 && xi <= n && yi >= 1 && yi <= m) {
++sum3[x][y];
q.push(make_pair(xi, yi));
}
}
}
}
}
memset(v, 0, sizeof(v));
for (register int i = 1; i <= n; ++i)
for (register int j = 1; j <= m; ++j) {
if (mp[i][j] == '1' && !v[i][j]) {
q.push(make_pair(i, j));
while (q.size()) {
int x = q.front().first, y = q.front().second;
int xi, yi;
v[x][y] = true;
q.pop();
for (register int i = 2; i <= 2; ++i) {
xi = x + step[i][0];
yi = y;
if (mp[xi][yi] == '1' && !v[xi][yi] && xi >= 1 && xi <= n && yi >= 1 && yi <= m) {
++sum2[x][y];
q.push(make_pair(xi, yi));
}
}
}
}
}
}
inline void test(int S[][MAXN]) {
cerr << "-----##-----" << endl;
for (register int i = 1; i <= n; ++i) {
for (register int j = 1; j <= m; ++j) cerr << S[i][j] << " ";
cerr << endl;
}
}
inline void sum(int S[][MAXN]) {
for (register int i = 1; i <= n; ++i)
for (register int j = 1; j <= m; ++j) S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + S[i][j];
}
inline int calc(int s[][MAXN], const int &a, const int &b, const int &c, const int &d) {
register int t = s[c][d] + s[a - 1][b - 1] - s[c][b - 1] - s[a - 1][d];
return t;
}
int main() {
#ifdef lky233
freopen("duty.in", "r", stdin);
freopen("duty.out", "w", stdout);
#endif
scanf("%d %d %d", &n, &m, &q);
for (register int i = 1; i <= n; ++i) scanf("%s", mp[i] + 1);
bfs();
for (register int i = 1; i <= n; ++i)
for (register int j = 1; j <= m; ++j)
sum1[i][j] = sum1[i - 1][j] + sum1[i][j - 1] - sum1[i - 1][j - 1] + (mp[i][j] - '0');
sum(sum2);
sum(sum3);
for (register int i = 1, a, b, c, d; i <= q; ++i) {
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("%d
", calc(sum1, a, b, c, d) - calc(sum2, a, b, c - 1, d) - calc(sum3, a, b, c, d - 1));
}
}
T3飞
空间限制 (32MB)
liu_runda 决定提高一下知识水平,于是他去请教郭神.郭神随手就给了 liu_runda 一道神题,liu_runda 并不会做,于是把这个题扔到联考里给高二的做。
郭神有(n)条位于第一象限内的线段,给出每条线段与(x)轴和(y)轴交点的坐标,显然这样就可以唯一确定每一条线段。
(n)条线段和(y)轴交点的纵坐标分别为(1,2,3,4...n)。我们记和(y)轴交点纵坐标为(i)的线段和(x)轴交点的横坐标为(x[i] + 1)按这样的方式生成:
(x[1])由输入给出。$$x[i] = (x[i-1]+a),2 leq i leq n$$即:如果(x[3] = 4),则与(y)轴交点纵坐标为(3)的抛物线和(x)轴交点的横坐标为(4+1=5)。
我们保证给出的(n,x[1],a,mod)使得所有的(x[1])互不相同。
对于第一象限内的所有点(点的横纵坐标可以是任意实数),如果一个点被(x)条线段经过, 它的鬼畜值就是(frac {x imes (x-1)}{2})。
第求一象限内的所有点的鬼畜值之和。
怎么做呢
首先分析这个有些特点的算式。我们发现如果两条线相交,会产生1的贡献,而如果三条线交于一点,会产生3的贡献,与三条线分别相交等价,以此类推,这个题被转化为求出现了几次两条线相交的情况。
由于线在y轴每次加一,每条线只会与x比它大的线相交。至此,这个问题转化为了一维的问题。
之后的推理有两种思路。
按照插入时间来对x排序,我们发现,当x与其余x逆序时,会产生他前面的线个数的贡献,这样这个问题就变成了求逆序对的问题。
但是(mod leq 10^8),直接做是放不下的。离散化?(n leq 10^7),如果开一个(1e7)的数组空间依旧爆炸。
分析(x)这个数列,在(x)被取模之前,这一段区间是递增的,这段区间内不会产生贡献,而它会与上一个区间对应位置之后的数产生贡献,对区间内的每个数来说,这个贡献成等差数列,可以快速进行处理。
对应位置产生的贡献,由于每段区间是规律的,因此我们仅记录每段区间起始位置的(x)形成的逆序对个数,每个逆序对将会产生该区间长度的贡献。由于(x1)的限制,第一段并不完整, 需要单独计算。(a)的范围是(1e5),可以轻松通过此题。
感谢 (@jiqimao)大佬提供了这个优秀的算法。
这个是我的差劲算法:
按照(x)的位置进行排序,我们发现每个(x)所产生的贡献是他后边x的个数。同样的在取模前这一段内,(x)递增,而每次(x+a)后,排在(x)后的数将会减去之前的区间段数。因此一段中(x)的贡献是等差数列,循环每一段,就可以得到答案。
劣质代码
等差数列边界处理太麻烦调完再发。这个是暴力跑的
调不出来咕咕咕了
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5 + 5;
int A[MAXN];
int a, mod, x1, n;
long long ans;
inline void add(int x) {
for (; x <= a; x += x & (~x + 1)) A[x] += 1;
}
inline int ask(int x) {
int ans = 0;
for (; x; x -= x & (~x + 1)) ans += A[x];
return ans;
}
inline int calc(int a1, int d, int n) { return a1 * n + (n * (n - 1)) * d; }
signed main() {
cin >> n >> x1 >> a >> mod;
for (register int i = 1, now = x1, cnt = 0, lun = 0; i <= n; ++i) {
if (now >= a) {
cnt -= lun;
if (x1 > now)
++cnt;
ans += cnt;
} else
cnt = i - 1 - ask(now + 1), ans += cnt, add(now + 1);
now += a;
if (now >= mod)
now -= mod, ++lun;
}
cout << ans << endl;
}