【ICPC2021济南】E Insidemen

题目连接:

​​​​​​​​​​​PTA | 程序设计类实验辅助教学平台千名教师建设,万道高质量题目,百万用户拼题的程序设计实验辅助教学平台https://pintia.cn/problem-sets/1459829212832296960/problems/1459829264400629764

题目大意:

        n个点均分一个圆,编号是1-n,给m条边,若由i,j和p,q组成的两条线相交,交点的贡献是(i + j ) * (p * q),删去两个点,问剩下的交点权值最大为多少。(N < =1000, M <= 10000)

解题思路:

        我们可以很容易地想到,权值最大为总的权值和-第一个点贡献的权值和-第二个点贡献的权值和+第一个点和第二个点共同作用贡献的权值和。

        线(i,j)将圆分为两个圆弧,要使与其有交点,对于线(s,t)需要满足s∈(i,j),t∈(j,i)即两段圆弧。那么对于线(i,j)的贡献 = \sum s\in \left ( i ,j \right ),t \in \left(j,i \right ) (s + t)  * (i + j)

        对于前者,其实是邻接矩阵上的一个子矩阵,可以用二维前缀和求出。

        由于在统计时,一个点被两条边算入贡献,因此总的权值和要除以二。

        我们再枚举点对,删除点i时,点j的贡献是多少,再取max。

        具体实现看代码

代码实现:

#include 
typedef long long ll;
using namespace std;
const int N = 1e3 + 100;
const int M = 1e5 + 100;
int n, m, g[N][N];
ll sum[N], du[N];//sum数组表示,删去点i的贡献值
struct node {
    int x, y;
} line[M];
int cal(int xa, int ya, int xb, int yb) { //计算邻接矩阵中的子矩阵
    if (xa > xb || ya > yb) return 0;
    return g[xb][yb] - g[xa - 1][yb] - g[xb][ya - 1] + g[xa - 1][ya - 1];
}
int ask(int x, int y) { //计算与(x,y)有交点的线的贡献。
    if (cal(x, y, x, y) == 0) return 0;
    if (x > y) swap(x, y);
    return cal(x + 1, y + 1, y - 1, n) + cal(x + 1, 1, y - 1, x - 1);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        if (x > y) swap(x, y);
        line[i].x = x;
        line[i].y = y;
        g[x][y] += x + y;
        g[y][x] += x + y;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1];
        }
    }
    ll num = 0;
    for (int i = 1; i <= m; i++) {
        num += 1ll * (line[i].x + line[i].y) * ask(line[i].x, line[i].y);
    }
    num /= 2;
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i == line[j].x || i == line[j].y) continue;
            if (line[j].x + 1 == line[j].y) continue;
            if (i > line[j].x && i < line[j].y) {
                sum[i] += 1ll * (line[j].x + line[j].y) * (cal(i, 1, i, line[j].x - 1) + cal(i, line[j].y + 1, i, n));
            } else {
                sum[i] += 1ll * (line[j].x + line[j].y) * cal(i, line[j].x + 1, i, line[j].y - 1);
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        memset(du, 0, sizeof(du));
        for (int j = 1; j <= m; j++) {
            if (i == line[j].x || i == line[j].y) continue;
            if (line[j].x + 1 == line[j].y) continue;
            ll t = 0;
            if (i > line[j].x && i < line[j].y) {
                t = 1ll * (line[j].x + line[j].y) * (cal(i, 1, i, line[j].x - 1) + cal(i, line[j].y + 1, i, n));
            } else {
                t = 1ll * (line[j].x + line[j].y) * cal(i, line[j].x + 1, i, line[j].y - 1);
            }
            du[line[j].x] += t;
            du[line[j].y] += t;
        }
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            ans = max(ans, num - (sum[i] + sum[j] - du[j] - (i + j) * ask(i, j)));
            // num 总贡献 
            //(sum[i] + sum[j]) 点i和j独立作用的权值和 
            // du[j] + (i + j) * ask(i,j) 点i和j共同作用的权值和 
        }
    }
    printf("%lld\n", ans);
    return 0;
}

你可能感兴趣的:(题解,算法)