题目就是求最多有几个连续区间之和为p的倍数,换个角度看问题就是,把是p的倍数的区间都列出来,当成一条条线段,求不相交线段最多可选几条。用贪心法,按照线段的右端点排序,从左到右能选就选。如果与之前的线段发生了冲突,则必定不选,因为如果选了,至少使得前面的这条线段会删去,而后续可选的线段也有变少的可能性。
用map来维护前缀和,记录每次前缀和的数值%p,如果再次遇到相同的数值,说明正好横跨一个p的长度,也就是目前这个元素的位置能作为一个p倍数线段的右端点,答案+1,同时要清除map并且再次从0开始求前缀和(之前的点对后面无影响_
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int T, n, p;
ll a[maxn], sum[maxn];
int main(void) {
scanf("%d", &T);
while (T--){
scanf("%d%d", &n, &p);
map<ll, int>mp;
mp[0] = 1;
int ans = 0;
for (int i = 1, sum = 0; i <= n; i++) {
scanf("%d", &a[i]);
a[i] %= p;
sum = (sum + a[i]) % p;
if (mp.count(sum)) {
ans++;
mp.clear();
mp[0] = 1;
sum = 0;
}
else mp[sum] = 1;
}
printf("%d\n", ans);
}
return 0;
}
每次介绍两个人认识,新集合的贡献不需要额外计算,因为在之前,这两个人所在的集合可以看作一个整体或者看作两个部分,对其它集合的影响都不变。会变得不合法的贡献只有两个集合之间各取一个人的情况,所以删除该不合法的情况即可。
#include
using namespace std;
typedef long long ll;
const ll p = 1e9 + 7;
const int maxn = 1e5 + 10;
int fa[maxn], a, num1[maxn], num2[maxn];
int T, n, u, v;
ll ans, sum2, sum1;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void merge(int x, int y) {
x = find(x); y = find(y);
ll k = 0;
k = (k + 1LL * num1[x] * num2[y] * (sum2 - num2[x] - num2[y])) % p;
k = (k + 1LL * num2[x] * num1[y] * (sum2 - num2[x] - num2[y])) % p;
k = (k + 1LL * num2[x] * num2[y] * (sum2 - num2[x] - num2[y])) % p;
k = (k + 1LL * num2[x] * num2[y] * (sum1 - num1[x] - num1[y])) % p;
ans = (ans - k + p) % p;
fa[y] = x;
num1[x] += num1[y]; num2[x] += num2[y];
num1[y] = 0; num2[y] = 0;
printf("%lld\n", ans);
}
inline int read() {
int x = 0; char c = getchar();
while (c < '0' || c>'9') c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
inline ll C_2(int n) {
if (n < 2)return 0;
return (ll)(n - 1) * n / 2 % p;
}
inline ll C_3(int n) {
if (n < 3)return 0;
return (ll)(n - 2) * (n - 1) * n / 6 % p;
}
int main(void) {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
T = read();
while (T--) {
n = read();
sum1 = sum2 = 0;
for (register int i = 1; i <= n; i++) {
fa[i] = i; a = read();
if (a == 1)num1[i] = 1, num2[i] = 0, sum1++;
else num2[i] = 1, num1[i] = 0, sum2++;
}
ans = (C_2(sum2) * sum1 % p + C_3(sum2)) % p;
printf("%lld\n", ans);
n--;
while (n--) {
u = read(); v = read();
merge(u, v);
}
}
return 0;
}
Dijkstra+DFS,因为是随机的边,所以最短路的边数很少。
DFS有k层每层都枚举一条边即可。
#include
#include
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 55;
int T, n, k, g[maxn][maxn];
int u, v, w, pre[maxn], ans;
struct node{
int id, dis;
node() {}
node(int id, int dis) :id(id), dis(dis) {}
bool operator < (const node& B)const {
return dis == B.dis ? id < B.id : dis > B.dis;
}
};
int dijkstra(int cmd = 0) {
int s = 1;
int dis[maxn], done[maxn];
for (int i = 1; i <= n; i++) {
dis[i] = inf; done[i] = 0;
}
dis[s] = 0;
priority_queue<node>pq;
pq.push(node(s, 0));
while (!pq.empty()) {
node t = pq.top(); pq.pop();
if (t.id == n)return t.dis;
if (done[t.id])continue;
done[t.id] = 1;
for (int i = 1; i <= n; i++) {
if (done[i])continue;
if (t.dis + g[t.id][i] < dis[i]) {
dis[i] = t.dis + g[t.id][i];
pq.push(node(i, dis[i]));
if (!cmd)pre[i] = t.id;
}
}
}
}
void dfs(int k) {
int t = dijkstra();
if (k == 0) {
ans = max(ans, t);
return;
}
int x = n, maxx = 0, set = 0;
int from[maxn], to[maxn], cnt = 0;
while (pre[x]){
from[cnt] = pre[x];
to[cnt++] = x;
x = pre[x];
}
for (int i = 0; i < cnt; i++) {
int bef = g[from[i]][to[i]];
g[from[i]][to[i]] = g[to[i]][from[i]] = inf;
dfs(k - 1);
g[from[i]][to[i]] = g[to[i]][from[i]] = bef;
}
}
int main(void) {
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
scanf("%d%d%d", &u, &v, &w);
g[u][v] = g[v][u] = w;
}
}
ans = 0;
dfs(k);
printf("%d\n", ans);
}
return 0;
}
平面镜对称铺路,重点是旋转120度,为了保证三角形3条边的方向的贡献都加上。 旋转坐标轴详细请看:https://blog.csdn.net/a6333230/article/details/88343282
每次只考虑平行x轴的方向,向下取整求y/h,如果是负方向,负数浮点数向下取整为-1,因此会贡献自动加1。
#include
#include
#include
using namespace std;
const double eps = 1e-6;
const double pi = acos(-1.0);
const double base = 2 * pi / 3;
int T, k;
double L, sx, sy, vx, vy, h;
double get_dis(double x,double y, double rad){
return -x * sin(rad) + (y - h / 3) * cos(rad) + h / 3;
}
bool ck(double times){
long long has = 0;
has += abs(floor(get_dis(sx + vx * times, sy + vy * times, base * 0) / h));
has += abs(floor(get_dis(sx + vx * times, sy + vy * times, base * 1) / h));
has += abs(floor(get_dis(sx + vx * times, sy + vy * times, base * 2) / h));
return has >= k;
}
int main(void){
scanf("%d", &T);
while (T--){
scanf("%lf %lf %lf %lf %lf %d", &L, &sx, &sy, &vx, &vy, &k);
h = L * sqrt(3) / 2;
double l = 0, r = 1e10, mid = (l + r) / 2;
while (r - l > eps){
if (ck(mid)) r = mid;
else l = mid;
mid = (l + r) / 2;
}
printf("%.8lf\n", (l + r) / 2);
}
return 0;
}
为了保证字典序,左括号总往最左边能加的地方加,右括号总在最右边能加的地方加,因为原字符串的括号不能动,所以必须加括号使得字符串的括号匹配,从左到右,从右到左都遍历一遍,遇到不平衡就加括号。
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 5;
char s[maxn], ans[maxn];
int T;
int main(void) {
scanf("%d", &T);
while (T--) {
scanf("%s", s + 1);
int len = strlen(s + 1), flag = 0;
for (register int i = 1,bef=1, num = 0; i <= len; i++) {
if (s[i] == '(')num++;
else if (s[i] == ')')num--;
if (num < 0)
for (int j = bef; j < i; j++)
if (s[j] == '*') {
s[j] = '(';
bef = j + 1;
num++;
break;
}
if (num < 0) {
flag = 1;
break;
}
}
for (register int i = len, bef = len, num = 0; i; i--) {
if (s[i] == ')')num++;
else if (s[i] == '(')num--;
if (num < 0)
for (int j = bef; j > i; j--)
if (s[j] == '*') {
s[j] = ')';
bef = j - 1;
num++;
break;
}
if (num < 0) {
flag = 1;
break;
}
}
if (flag)printf("No solution!");
else for (int i = 1; i <= len; i++)
if (s[i] != '*')printf("%c", s[i]);
printf("\n");
}
return 0;
}