最近CF有点炸。。。打比赛都好麻烦啊。。。
有 N N N条形如 y = x + p i ( 0 ≤ p i ≤ 1 0 9 ) y=x+p_i(0\le p_i\le 10^9) y=x+pi(0≤pi≤109)的直线, M M M条形如 y = − x + q i ( 0 ≤ q I ≤ 1 0 9 ) y=-x+q_i(0\le q_I\le10^9) y=−x+qi(0≤qI≤109)的直线。求这些直线一共有多少个交点横纵坐标均为整点。
显然只有 y = x + p i y=x+p_i y=x+pi和 y = − x + q j y=-x+q_j y=−x+qj才可能有一个交点,那么我们联立解析式,解一下这个方程组得到: { x = q j − p i 2 y = p i + q j 2 \begin{cases}x=\frac{q_j-p_i}{2}\\y=\frac{p_i+q_j}{2}\end{cases} {x=2qj−piy=2pi+qj
显然只有当 q j q_j qj和 p i p_i pi奇偶性相同的时候,方程组的解才是整数,于是统计 p i , q j p_i,q_j pi,qj的奇偶性即可。
#include
#include
using namespace std;
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T;
scanf("%d", &T);
while(T--) {
int N, M;
int cnt0 = 0, cnt1 = 0;
scanf("%d", &N);
for(int i = 1; i <= N; i++) {
int p;
scanf("%d", &p);
if(p % 2 == 0) cnt0++;
else cnt1++;
}
scanf("%d", &M);
long long ans = 0;
for(int i = 1; i <= M; i++) {
int p;
scanf("%d", &p);
if(p % 2) ans += cnt1;
else ans += cnt0;
}
printf("%lld\n", ans);
}
return 0;
}
给定 N N N根木棍的长度,现在要求将这 N N N根木棍平行于 x x x轴或者平行于 y y y轴放置,要求不能够有连续两根以上的木棍状态是一样的(即只能够横竖横竖或者竖横竖横地放),求最后放的木棍的终点到原点 ( 0 , 0 ) (0,0) (0,0)的距离的最大值的平方。
通过简单的数学推理可以得到,我们需要将这 N N N根木棍分成两部分。设第一部分的总长为 X X X,第二部分的总长为 Y Y Y,我们的目的是最大化 X 2 + Y 2 X^2+Y^2 X2+Y2。
结论: 当我们最大化 X X X或者 Y Y Y中的一个值时,答案一定是最大的。
证明: 不妨设 X ≤ Y X\le Y X≤Y。我们从 X X X中拿出总长度为 p p p的木棍放进 Y Y Y中,那么新的距离的平方为 ( X − p ) 2 + ( Y + p ) 2 = X 2 − 2 X p + p 2 + Y 2 + 2 Y p + p 2 = X 2 + Y 2 + 2 p ( Y − X + p ) (X-p)^2+(Y+p)^2=X^2-2Xp+p^2+Y^2+2Yp+p^2=X^2+Y^2+2p(Y-X+p) (X−p)2+(Y+p)2=X2−2Xp+p2+Y2+2Yp+p2=X2+Y2+2p(Y−X+p)。显然答案增大。
由于要求只能够横竖横竖地放,我们只需要将前 N 2 \frac{N}{2} 2N小的木棍的长度和作为 X X X,剩余的作为 Y Y Y就可以了。
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 100000;
int N, A[Maxn + 5];
ll s[Maxn + 5];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d", &N);
for(int i = 1; i <= N; i++)
scanf("%d", &A[i]);
sort(A + 1, A + N + 1);
for(int i = 1; i <= N; i++)
s[i] = s[i - 1] + A[i];
int mid = N / 2;
ll x = s[mid] - s[0], y = s[N] - s[mid];
printf("%lld\n", x * x + y * y);
return 0;
}
给定一个 N × M N\times M N×M的方格图,现在要求将每个格子染成黑白两种颜色,要求每个格子至多和四个方向(上下左右)上的一个格子同色,问有多少种方案。
我们可以知道,当我们确定了第一行和第一列,那么整个方格图就确定了。
现在我们开始统计一行和一列的方案数。
考虑仅确定一行的方案数。
我们设 f ( i ) f(i) f(i)为长度为 i i i的方格的合法方案数。
由于我们可以在方格后加上一个黑色或者白色的方格(强制与最后一个方格不同色),所以我们可以用 f ( i ) f(i) f(i)去更新 f ( i + 1 ) f(i+1) f(i+1),而且这个方格的颜色是唯一的,所以 f ( i + 1 ) + = f ( i ) f(i+1)+=f(i) f(i+1)+=f(i)
我们也可以加上两个连续的黑色或者白色的方格。这样,我们可以用 f ( i ) f(i) f(i)去更新 f ( i + 2 ) f(i+2) f(i+2),所以有 f ( i + 2 ) + = f ( i ) f(i+2)+=f(i) f(i+2)+=f(i)。
简单整理一下上面两个方程,我们可以得到 f ( i ) = f ( i − 1 ) + f ( i − 2 ) f(i)=f(i-1)+f(i-2) f(i)=f(i−1)+f(i−2)。
这不就是斐波那契数列的递推公式吗?
又因为当行和列拼在一起时会导致格子 ( 1 , 1 ) , ( 1 , 2 ) , ( 2 , 1 ) (1,1),(1,2),(2,1) (1,1),(1,2),(2,1)三个格子同色,所以我们的答案是 f ( N ) + f ( M ) − 1 f(N)+f(M)-1 f(N)+f(M)−1。
又由于黑白可以反色,所以总答案为 2 ( f ( N ) + f ( M ) − 1 ) 2(f(N)+f(M)-1) 2(f(N)+f(M)−1)。
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 100000;
const ll Mod = 1e9 + 7;
ll fib[Maxn + 5];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ll N, M;
scanf("%lld %lld", &N, &M);
fib[0] = fib[1] = 1;
for(int i = 2; i <= max(N, M); i++)
fib[i] = (fib[i - 1] + fib[i - 2]) % Mod;
printf("%lld\n", 2 * (fib[N] + fib[M] - 1 + Mod) % Mod);
return 0;
}
有一个括号序列,你需要选择两个位置,将位置上的括号交换,使得最后得到的循环位置最大。
循环位置的定义是:我们将前 i i i个位置上的字符串取出放到字符串的最后,所得的括号序列是一个合法的序列。
首先我们可以知道,对于一个左右括号数量并不相同的序列,它没有循环位置。我们将这种情况直接特判掉。
我们先将整个括号序列转成一个合法的序列。容易证明这是一定存在的。
那么我们可以发现,对于若我们交换一个一级括号的左右括号,如下:
若我们记翻转的一级括号内的二级括号数量为 a a a,那么这个操作的答案就是 a + 1 a+1 a+1。
我们也可以交换一个二级括号的左右括号。
简单画图可以知道答案是一级括号的数量+翻转的二级括号内的数量+1。
通过画图可以发现翻转一个三级括号是没用的。
O ( N ) O(N) O(N)扫一遍就行了。
#include
#include
using namespace std;
const int Maxn = 300000;
int N, s[Maxn * 2 + 5];
char str[Maxn + 5];
int lef[Maxn * 2 + 5], dep[Maxn * 2 + 5];
int siz[Maxn * 2 + 5], sum[Maxn * 2 + 5];
int stk[Maxn + 5], tp;
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d %s", &N, str + 1);
int cnt = 0;
for(int i = 1; i <= N; i++) {
s[i] = s[i + N] = (str[i] == '(' ? 1 : -1);
cnt += s[i];
}
if(cnt) {
puts("0\n1 1");
return 0;
}
int pos = 1;
for(int i = 1; i <= N * 2; i++) {
cnt += s[i];
if(cnt < 0) pos = i + 1, cnt = 0;
else if(i - pos + 1 >= N) break;
}
for(int i = pos; i <= pos + N - 1; i++)
if(s[i] == -1) {
int p = stk[tp--];
sum[tp]++, lef[i] = p, dep[i] = tp;
siz[i] = sum[tp + 1], sum[tp + 1] = 0;
} else stk[++tp] = i;
int ans = sum[0], l = 1, r = 1;
for(int i = pos; i <= pos + N - 1; i++) {
if(dep[i] == 0)
if(siz[i] + 1 > ans)
ans = siz[i] + 1, l = lef[i], r = i;
if(dep[i] == 1)
if(sum[0] + siz[i] + 1 > ans)
ans = sum[0] + siz[i] + 1, l = lef[i], r = i;
}
printf("%d\n%d %d\n", ans, (l - 1) % N + 1, (r - 1) % N + 1);
return 0;
}
有 N N N个人在火车上坐成一列,第一个位置旁边有一个水龙头。
第 i i i个人会在 t i t_i ti时刻去接水。每个人去接水都会用 p p p分钟。如果他前面有人在排队或者接水,那么他会等待,否则就去接水。问每个人接完水的时间。
我们用两个队列来模拟这个过程。一个用来存当前正在排队的人,另一个用来存在座位上等待的人。
我们先把人按照前去接水的顺序排好。
当排队队列不是空的时候,我们将队头取出,更新它的答案。
否则当当前时间大于他前去接水时,检查队列,若队列为空或者他前面没有人去排队时就把他塞进队列中,否则放到优先队列里。
否则从优先队列中取出一个元素并更新它的答案。若优先队列也是空的,那么就将时间倒回当前这个人去接水的时间即可。
#include
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 100000;
struct Node {
int t;
int id;
};
bool cmp(Node lhs, Node rhs) {return lhs.t == rhs.t ? lhs.id < rhs.id : lhs.t < rhs.t;}
int N, P;
Node A[Maxn + 5];
queue<int> q1;
priority_queue<int, vector<int>, greater<int> > q2;
ll ans[Maxn + 5];
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d %d", &N, &P);
for(int i = 1; i <= N; i++) {
scanf("%d", &A[i].t);
A[i].id = i;
}
sort(A + 1, A + N + 1, cmp);
ll now = 0;
int last = N + 1;
for(int i = 1; i <= N;) {
if(!q1.empty()) {
int id = q1.front();
q1.pop();
ans[id] = now + P;
now += P, last = id;
} else if(A[i].t <= now) {
if(q1.empty() && A[i].id < last)
q1.push(A[i].id);
else if(!q1.empty() && q1.back() > A[i].id && A[i].id < last)
q1.push(A[i].id);
else q2.push(A[i].id);
i++;
} else {
if(!q2.empty()) {
int id = q2.top();
q2.pop();
ans[id] = now + P;
now += P, last = id;
} else now = A[i].t, last = N + 1;
}
}
while(!q1.empty()) {
int id = q1.front();
q1.pop(), ans[id] = now + P;
now += P;
}
while(!q2.empty()) {
int id = q2.top();
q2.pop(), ans[id] = now + P;
now += P;
}
for(int i = 1; i <= N; i++)
printf("%lld ", ans[i]);
return 0;
}
有 N N N个人和 N N N只猫,每个人都认识一些猫(其中包括自己的猫)。要求选出 j j j裁判和 p p p只猫,使得 j + p = N j+p=N j+p=N,且选出的人和猫都互相不认识。输出任意一种方案。
我们将一个人和一只猫拆成两个点,每个猫点向他们对应的点连一条有向边,就像这样:
容易看出裁判集合无法到达选手集合。
我们就可以找出所有的强连通分量。如果只有一个强连通分量,那么就不存在答案。
否则我们随便找一个强连通分量作为裁判,剩余的作为选手即可。
其实我也不会证明这个结论。。。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int Maxn = 1e6;
int N, M;
vector<int> G[Maxn + 5];
int dfn[Maxn + 5], low[Maxn + 5], dcnt;
int col[Maxn + 5], ccnt;
bool vis[Maxn + 5];
int stk[Maxn + 5], tp;
void Tarjan_scc(int u) {
dfn[u] = ++dcnt, low[u] = dcnt, vis[u] = true;
stk[++tp] = u;
for(int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if(!dfn[v]) {
Tarjan_scc(v);
low[u] = min(low[u], low[v]);
} else if(vis[v]) low[u] = min(low[u], low[v]);
}
if(dfn[u] == low[u]) {
++ccnt;
while(true) {
col[stk[tp]] = ccnt;
vis[stk[tp]] = false;
if(stk[tp--] == u)
break;
}
}
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T;
scanf("%d", &T);
while(T--) {
scanf("%d %d", &N, &M);
ccnt = 0;
for(int i = 1; i <= N; i++)
dfn[i] = 0, G[i].clear();
for(int i = 1; i <= M; i++) {
int u, v;
scanf("%d %d", &u, &v);
if(u == v) continue;
G[u].push_back(v);
}
for(int i = 1; i <= N; i++)
if(!dfn[i]) Tarjan_scc(i);
if(ccnt == 1) puts("No");
else {
puts("Yes");
int cnt_j = 0, cnt_p = 0;
for(int i = 1; i <= N; i++)
if(col[i] == 1) cnt_j++;
else cnt_p++;
printf("%d %d\n", cnt_j, cnt_p);
for(int i = 1; i <= N; i++)
if(col[i] == 1) printf("%d ", i);
puts("");
for(int i = 1; i <= N; i++)
if(col[i] > 1) printf("%d ", i);
puts("");
}
}
return 0;
}