题目链接
不会。
考虑固定1号针,枚举2号针,计算3号针放置的方案数。
显然,3号针应在前面两个针的反向延长线围成的扇形区域内,这样可以放置的方案数就是种。此外当n为偶数且前两个针反向时,显然3号针可以放在剩余的任意位置上,即偶数的答案需要加上。
枚举1号针的位置,答案要乘上。
然后考虑针的顺序问题,三个长度都相同的时候除以,有两根相同的时候除以。
由于对取模直接用 自然溢出,这样除法用比较难用逆元处理,在除6操作的时候需要讨论一下运算顺序保证精度。
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ull n, ans;
int a, b, c;
int main()
{
cin >> a >> b >> c >> n;
ull hf = n >> 1;
if(n & 1) ans = hf*(hf+1);
else ans = ((hf+1)*hf/2 - 1)*2 + n - 2;
if(a == b && b == c)
{
ans /= 2;
if(ans % 3 == 0) ans = ans/3*n;
else ans = n/3*ans;
}
else if(a == b || a == c || b == c)
{
ans /= 2;
ans *= n;
}
else ans *= n;
cout << ans << endl;
return 0;
}
考虑构造一棵只由黑色点构成的树,边的权值就是原树中的距离。这样黑色点中两两最长的距离就是这棵树的直径。
首先Floyd预处理原树中点之间的距离,然后枚举新树的直径。假设新树的直径是点对之间的距离,然后枚举剩余的黑点。黑点k能被加入到新树中的条件显然是不能使直径增加,也就是且。
总的时间复杂度。
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 105;
const int INF = 0x3f3f3f3f;
int n, m, x, y;
int dis[maxn][maxn];
int isBlack[maxn];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++) dis[i][j] = INF;
for(int i = 1;i <= n;i++)
scanf("%d", &isBlack[i]), dis[i][i] = 0;
for(int i = 1;i < n;i++)
scanf("%d%d", &x, &y), dis[x][y] = dis[y][x] = 1;
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
int ans = INF;
for(int i = 1;i <= n;i++)
{
for(int j = i;j <= n;j++)
{
if(!isBlack[j] || !isBlack[i]) continue;
int cnt = 0;
for(int k = 1;k <= n;k++)
{
if(isBlack[k] && dis[i][k] <= dis[i][j] && dis[k][j] <= dis[i][j])
cnt++;
}
if(cnt >= m) ans = min(ans, dis[i][j]);
}
}
printf("%d\n", ans);
return 0;
}
考虑如果某个子树中的技能起止点一共有x个,那么当x为奇数的时候,这个子树的根与其父亲之间的边只需要走一次,否则就需要走两次。
那么令为按照题目要求遍历完以为根的子树,且子树内有个技能起始点的最小距离(不含技能时间),那么有:
(j & 1)
(! j & 1)
#include
#include
#include
#include
#include
#include
#include
考虑位于坐标的鱼,如果,那么肯定不能被捕捉到。
否则能够捕捉到这条鱼的渔夫应位于内。
将渔夫按照升序排序,对于每一条鱼找出能被捕捉到的渔夫区间然后进行一个区间加的操作。最后对所有渔夫单点查询即可。
#include
#include
#include
#include
考虑将一个区间的数全部变成某一端点的数,最多需要两步, 例如:
如果,则先对取,再对取。反之亦然。右端点同理。
考虑如果将一个区间的数全部变成某个数,那么这个数必然在原序列里出现。
那么中的每一个元素必然在中可以找到一个匹配的位置,且这个位置序列是非递减的。
此时就可以分成若干个子问题:若到全部匹配了,那么就需要将变为这个值。具体的操作只需分类讨论即可,可以发现总数目不会超过。
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int n, a[maxn], b[maxn], pos[maxn];
char ans[maxn<<1];
int no, tot, L[maxn<<1], R[maxn<<1];
struct node
{
int x, l, r;
}e[maxn];
void add(char c, int l, int r)
{
ans[no] = c, L[no] = l, R[no] = r;
no++;
}
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
for(int i = 1;i <= n;i++) scanf("%d", &b[i]);
int now = 1;
for(int i = 1;i <= n;i++)
{
while(now <= n && a[now] != b[i]) now++;
if(now > n) {puts("-1"); return 0;}
pos[i] = now;
}
now = 1, tot = no = 0;
for(int i = 1;i <= n;i++)
{
if(pos[i] == pos[i+1]) continue;
e[tot++] = (node){pos[i], now, i};
now = i + 1;
}
for(int i = tot-1;i >= 0;i--)
{
if(e[i].x >= e[i].l) continue;
if(a[e[i].x] >= a[e[i].x+1])
{
add('m', e[i].x+1, e[i].r);
add('M', e[i].x, e[i].r);
}
else
{
add('M', e[i].x+1, e[i].r);
add('m', e[i].x, e[i].r);
}
}
for(int i = 0;i < tot;i++)
{
if(e[i].x <= e[i].r) continue;
if(a[e[i].x] <= a[e[i].x-1])
{
add('M', e[i].l, e[i].x-1);
add('m', e[i].l, e[i].x);
}
else
{
add('m', e[i].l, e[i].x-1);
add('M', e[i].l, e[i].x);
}
}
for(int i = 0;i < tot;i++)
{
if(e[i].l > e[i].x || e[i].r < e[i].x) continue;
if(e[i].x > e[i].l)
{
if(a[e[i].x] >= a[e[i].x-1])
{
add('m', e[i].l, e[i].x-1);
add('M', e[i].l, e[i].x);
}
else
{
add('M', e[i].l, e[i].x-1);
add('m', e[i].l, e[i].x);
}
}
if(e[i].x < e[i].r)
{
if(a[e[i].x] >= a[e[i].x+1])
{
add('m', e[i].x+1, e[i].r);
add('M', e[i].x, e[i].r);
}
else
{
add('M', e[i].x+1, e[i].r);
add('m', e[i].x, e[i].r);
}
}
}
printf("%d\n", no);
for(int i = 0;i < no;i++)
printf("%c %d %d\n", ans[i], L[i], R[i]);
return 0;
}
根据矩阵贡献是四个子矩阵贡献之和+1这一条件,猜想结论:如果一个矩阵的所有子矩阵中,有两种颜色(不纯)的子矩阵的数目是,那么这个矩阵的贡献就是。
下面用数学归纳法证明这个结论。
对于,显然不纯的矩阵数为0且贡献为1,结论成立。
假设时结论成立,此时设阶矩阵的四个阶子矩阵中不纯的子矩阵数目分别为,显然总的数目为。每个子矩阵的贡献分别为,总的贡献为即。即时结论也成立。
综上此结论成立。
现在要统计不纯的子矩阵数目,用所有矩阵数目减去纯的矩阵数目。考虑如果存在阶纯的矩阵,显然要有连续行和列被改变的数目的奇偶性相同。于是就只需要统计有多少连续的行与列改变了相同奇偶性的次数,二者的乘积就是阶纯的矩阵的数目。这个操作可以用线段树来实现。
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = (1 << 20) + 5;
int n, m, sz, op, x;
ll tree[2][maxn << 2], ans[2][21];
void update(int op, int rt, int l, int r, int deep, int aim)
{
if(l == r) {tree[op][rt] ^= 1; return;}
int mid = (l + r) >> 1;
if(aim <= mid) update(op, rt<<1, l, mid, deep+1, aim);
else update(op, rt<<1|1, mid+1, r, deep+1, aim);
if(tree[op][rt] != -1) ans[op][deep]--;
if(tree[op][rt<<1] == tree[op][rt<<1|1]) tree[op][rt] = tree[op][rt<<1];
else tree[op][rt] = -1;
if(tree[op][rt] != -1) ans[op][deep]++;
}
int main()
{
scanf("%d%d", &sz, &m);
n = 1 << sz;
memset(tree, 0, sizeof(tree));
ll sum = 0;
for(int i = 1;i <= sz;i++)
{
ans[0][i] = ans[1][i] = 1 << (i-1);
sum += (1LL << (i*2-2));
}
for(int i = 1;i <= m;i++)
{
scanf("%d%d", &op, &x);
update(op, 1, 1, n, 1, x);
ll now = 0;
for(int j = 1;j <= sz;j++)
now += ans[0][j]*ans[1][j];
ll ans = 4*(sum - now) + 1;
printf("%I64d\n", ans);
}
return 0;
}
正经解法不会。毕竟这题有spj,写了个随机给莽过去了。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 200050;
int t, n, m;
int u[maxn], v[maxn], flag[maxn];
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= m;i++)
scanf("%d%d", &u[i], &v[i]);
int num = m/4 + 1, now = 0;
while(now < num)
{
now = 0;
for(int i = 1;i <= n;i++)
flag[i] = rand() % 2;
for(int i = 1;i <= m;i++)
{
if(flag[u[i]] && !flag[v[i]])
now++;
}
}
printf("%d\n", now);
int cnt = 0;
for(int i = 1;i <= m;i++)
{
if(flag[u[i]] && !flag[v[i]])
{
printf("%d", i);
cnt++;
if(cnt == now) printf("\n");
else printf(" ");
}
}
}
return 0;
}
根据逆序对的性质首先可以还原出原序列。
根据定义,一个合法的序列必然是单调递增,且最小值的左侧都比它大,最大值的右侧都比它小。
考虑表示以为序列的最后一个点,合法的方案数。,且 且 不存在满足。
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 105;
int n, m, u, v, a[maxn], in[maxn];
ll dp[maxn];
bool vis[maxn];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= m;i++)
{
scanf("%d%d", &u, &v);
if(u < v) swap(u, v);
in[v]++;
}
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(vis[j]) continue;
if(in[i]) in[i]--;
else {a[i] = j, vis[j] = 1; break;}
}
}
for(int i = 1;i <= n;i++)
{
ll res = 0, maxx = 0;
for(int j = i-1;j >= 1;j--)
{
if(a[j] > a[i]) continue;
if(a[j] > maxx) res += dp[j], maxx = a[j];
}
dp[i] = max(1LL, res);
}
ll ans = 0, maxx = 0;
for(int i = n;i >= 1;i--)
{
if(a[i] > maxx)
ans += dp[i], maxx = a[i];
}
printf("%I64d\n", ans);
return 0;
}
首先反向dijkstra处理出兔子从每个点到终点的最短路长度,然后线性遍历一遍兔子路径上的点,依次判断此处作弊是否合法即可,时间复杂度。
不是很懂现场赛为什么过的这么少……大概歪榜了吧(滑稽
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 200050;
const int maxm = 400050;
const ll INF = 1LL << 60;
int n, m, u, v, t, r, pr, pt, vis[maxn];
int tPath[maxn], tSleep[maxn], rPath[maxn];
ll dis[maxn], sum[maxn];
vector G[maxn], W[maxn], ans;
struct node
{
int u, v, t, r;
}e[maxm];
struct point
{
int x;
bool operator < (const point &o) const
{
return dis[x] > dis[o.x];
}
};
void dijkstra(int s)
{
memset(vis, 0, sizeof(vis));
for(int i = 1;i <= n;i++) dis[i] = INF;
priority_queue que;
dis[s] = 0, vis[s] = 1;
que.push((point){s});
while(!que.empty())
{
point now = que.top();
que.pop();
int u = now.x;
vis[u] = 0;
for(int i = 0;i < G[u].size();i++)
{
int v = G[u][i];
if(dis[v] > dis[u] + W[u][i])
{
dis[v] = dis[u] + W[u][i];
if(!vis[v]) {vis[v] = 1; que.push((point){v});}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= m;i++)
{
scanf("%d%d%d%d", &u, &v, &t, &r);
G[v].push_back(u);
W[v].push_back(r);
e[i] = (node){u, v, t, r};
}
scanf("%d", &pt);
for(int i = 1;i <= pt;i++)
scanf("%d%d", &tPath[i], &tSleep[i]);
scanf("%d", &pr);
for(int i = 1;i <= pr;i++) scanf("%d", &rPath[i]);
dijkstra(n);
for(int i = pt;i >= 1;i--) sum[i] = sum[i+1] + e[tPath[i]].t;
int now = 1;
ll tCnt = 0, rCnt = 0;
for(int i = 1;i <= pr;i++)
{
int tp = rPath[i];
if(dis[e[tp].u] == dis[e[tp].v] + e[tp].r)
{
rCnt += e[tp].r;
continue;
}
while(now <= pt && tCnt + tSleep[now-1] + e[tPath[now]].t <= rCnt)
{
tCnt += tSleep[now-1] + e[tPath[now]].t;
now++;
}
if(tCnt + tSleep[now-1] + sum[now] >= rCnt + dis[e[tp].u])
ans.push_back(e[tp].u);
rCnt += e[tp].r;
}
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for(int i = 0;i < (int)ans.size();i++)
printf("%d ", ans[i]);
printf("\n");
return 0;
}
学习了一下大佬的博客orz,这种用二维线段树之类的空间会爆炸的问题基本上都可以用CDQ分治来解决。
CDQ分治和普通分治的区别就在于CDQ分治的左儿子的答案会对右儿子的答案产生影响,实际上归并排序就是一个标准的CDQ分治。
将点看作一个元素,矩形的左右边分别看作一个元素,然后在从左往右扫的时候,矩形的边和点之间相互会有贡献。比如在矩形之前加的点对矩形的左边有正贡献,对右边有负贡献。类似地,矩形的左边对点有正贡献,右边对点有负贡献。在统计的时候用树状数组维护这个贡献的区间和即可。注意如果x相同,按照矩形左边界-点-矩形右边界的顺序来进行。
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 4e5 + 10;
int n, o, x, y, _x, _y;
int no = 0, tot = 0, w[maxn];
int tree[maxn][2];
ll ans[maxn];
int lowbit(int x) {return x & (-x);}
struct node
{
int op, id, x, l, r;
bool operator < (const node& o) const
{
if(x != o.x) return x < o.x;
return op < o.op;
}
}e[maxn], now[maxn];
void add(int op, int p, int x)
{
while(p < maxn)
{
tree[p][op] += x;
p += lowbit(p);
}
}
void clr(int op, int p)
{
if(x <= 0) return;
while(p < maxn)
{
tree[p][op] = 0;
p += lowbit(p);
}
}
int sum(int op, int p)
{
int res = 0;
while(p)
{
res += tree[p][op];
p -= lowbit(p);
}
return res;
}
void solve(int l, int r)
{
if(l == r) return;
int mid = (l + r) >> 1;
solve(l, mid), solve(mid + 1, r);
vector va, vb;
int nu = 0, u = l, v = mid + 1;
while(u <= mid && v <= r)
{
if(e[u] < e[v])
{
if(e[u].op == 2)
{
add(0, e[u].r, 1);
va.push_back(e[u].r);
}
else if(e[u].op == 1)
{
add(1, e[u].l, 1);
add(1, e[u].r + 1, -1);
vb.push_back(e[u].l);
vb.push_back(e[u].r + 1);
}
else if(e[u].op == 3)
{
add(1, e[u].l, -1);
add(1, e[u].r + 1, 1);
vb.push_back(e[u].l);
vb.push_back(e[u].r + 1);
}
now[nu++] = e[u++];
}
else
{
if(e[v].op == 2) ans[e[v].id] += sum(1, e[v].r);
else if(e[v].op == 1) ans[e[v].id] -= sum(0, e[v].r) - sum(0, e[v].l-1);
else if(e[v].op == 3) ans[e[v].id] += sum(0, e[v].r) - sum(0, e[v].l-1);
now[nu++] = e[v++];
}
}
while(u <= mid) now[nu++] = e[u++];
while(v <= r)
{
if(e[v].op == 2) ans[e[v].id] += sum(1, e[v].r);
else if(e[v].op == 1) ans[e[v].id] -= sum(0, e[v].r) - sum(0, e[v].l-1);
else if(e[v].op == 3) ans[e[v].id] += sum(0, e[v].r) - sum(0, e[v].l-1);
now[nu++] = e[v++];
}
for(int i = 0;i < va.size();i++) clr(0, va[i]);
for(int i = 0;i < vb.size();i++) clr(1, vb[i]);
for(int i = l;i <= r;i++) e[i] = now[i-l];
}
int main()
{
scanf("%d", &n);
w[no++] = 0;
for(int i = 1;i <= n;i++)
{
scanf("%d%d%d", &o, &x, &y);
if(o == 1)
{
w[no++] = ++x, w[no++] = ++y;
e[tot++] = (node){2, i, x, 0, y};
}
else
{
scanf("%d%d", &_x, &_y);
w[no++] = ++x, w[no++] = ++y, w[no++] = ++_x, w[no++] = ++_y;
e[tot++] = (node){1, i, x, y, _y};
e[tot++] = (node){3, i, _x, y, _y};
}
}
sort(w, w + no);
no = unique(w, w + no) - w;
for(int i = 0;i < tot;i++)
{
e[i].x = lower_bound(w, w + no, e[i].x) - w;
e[i].l = lower_bound(w, w + no, e[i].l) - w;
e[i].r = lower_bound(w, w + no, e[i].r) - w;
}
solve(0, tot - 1);
for(int i = 1;i <= n;i++)
{
ans[i] += ans[i-1];
printf("%I64d\n", ans[i]);
}
return 0;
}