2016年湖南省第十二届大学生计算机程序设计竞赛 解题报告

这次省赛居然出题方居然不是刘汝佳而是换成了叉姐,现场上看到ICPCCAMP这几个字的时候我的内心是血崩的/(ㄒoㄒ)/~~。不过说实话,叉姐的题,有毒!,吸的我根本停不下来~


先发个做题地址:题目

再放一个大牛的一句话题解:别人的题解


反正题面大部分都是中文,我就不描述题意了(葛优躺)


Problem A: 2016

解法1:对于一个数a,可以表示成(a/2016*2016+a%2016)的形式,那么a和b相乘是否为2016的倍数只需看右边模的部分。于是只需把所有模数的个数算出来就ok了,复杂度:2016^2。

解法2:在现场写的解法,假设我知道了一个数a,那么与a相乘能为2016的倍数的b的个数为m/(2016/gcd(a,2016))。显然枚举a是不现实的,那么我们可以尝试取枚举gcd(a,2016)=k,然后看能满足的a有多少个,由于k的枚举量不超过2016,可行,那么如何求gcd(a,2016)=k的合法数呢。假如k=1的时候,相当于求n以内有多少个数与2016互素,这个容斥一下便能解决。然后对于gcd(a,2016)=2的合法数,等价于求gcd(a/2,2016/2)=1,即n/2以内与1008互素的个数,其余同理。然后我们就求出了gcd(a,2016)=k的a的个数,乘上对应的b的个数,枚举k并累加起来便是答案。复杂度:非常快


Problem B:有向无环图
观察式子count(i,j)*a[i]*b[j],对于一个点j来说,对答案的贡献可以表示成(∑count(i,j)*a[i])*b[j]。动手画一画可以发现(∑count(i,j)*a[i])是具有传递性和累加性的。我们令dp[u]表示u点上的(∑count(i,u)*a[i]),那么u点对于答案的贡献为dp[u]*b[u],对于一个u连接的点v,dp[u]对dp[v]的贡献为dp[u]+a[u]。然后对着图的拓扑序进行dp即可


Problem C:Three Capitals

解法1:从1号点出发,遍历每条边一次后回到1号点,人们称之为欧拉回路,那么这题实际上就是欧拉回路计数了,这个时候有个名为BEST theorem的定理可以求得。该定理表示一个有向图的欧拉回路数其中tw(G)=以w为根的树形图的个数,deg(v)为点v的出度数,其中tw(G)在一般情况下需要matrix-tree定理求得,不过这题的顶点只有3个,以1为根的树形图的数目手算都能算出来所以不需要matrix-tree定理(树形图是指在图上选择一些边使图上所有节点形成一颗树,不同的边组成的树形图也算不同)。由于BEST定理是有向图的,根据欧拉回路的性质,每个顶点的出度=入度,我们可以枚举从1号点指向2号点的边数量,从而给整张图定向。最后我们把所有的欧拉回路算出来后乘上点1的出度数便是答案。

这里为何要乘上1的出度呢,要注意欧拉回路是无始无终的,以下图为例

2016年湖南省第十二届大学生计算机程序设计竞赛 解题报告_第1张图片

可以看到下面两种边走的方式1->5->3->2->4和2->3->4->1->5是同属于一种欧拉回路的,所以对于每一种欧拉回路我们有deg(1)种不同方式

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 100000 + 10;

ll f[maxn];
void init() {
	f[0] = 1;
	for (int i = 1; i < maxn; ++i) {
		f[i] = f[i - 1] * i % mod;
	}
}

ll mypow(ll a, ll b) {
	ll ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return ans;
}

ll inv(ll x) {
	return mypow(x, mod - 2);
}

ll C(int n, int m) {
	return f[n] * inv(f[n - m]) % mod * inv(f[m]) % mod;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif
	
	init();
	
	int a, b, c;
	while (~scanf("%d%d%d", &a, &b, &c)) {
		if ((a + b) % 2 || (b + c) % 2 || (a + c) % 2) {
			puts("0");
			continue;
		}
		int dega = (a + b) / 2, degb = (a + c) / 2, degc = (b + c) / 2;
		
		ll ans = 0;
		ll ab, ac, ba, bc, ca, cb;
		for (int i = 0; i <= a; i++) {
			ab = i;
			ba = a - ab;
			ac = dega - ab;
			ca = b - ac;
			bc = degb - ba;
			cb = c - bc;
			if (ab >= 0 && ac >= 0 && ba >= 0 && bc >= 0 && ca >= 0 && cb >= 0) {
				ll cnt = (ab * ac + ab * bc + ac * cb) % mod;
				cnt = cnt * C(a, ab) % mod * C(b, ac) % mod * C(c, bc) % mod;
				ans += cnt * f[dega - 1] % mod * f[degb - 1] % mod * f[degc - 1] % mod;
				ans %= mod;
			}
		}
		cout << ans*dega % mod << endl;
	}
}

解法2:BEST定理毕竟不是很多人都知道的,叉姐固然考虑到了这点,表示此题不需要BEST定理也能做,可惜在下智商做鸡,目前还没想到(T_T)


Problem D:Toll

这题有个积分号,估计吓到了不少人,不过说实话真不算难,而且各种体位都能过。

解法1:比较标准的解法,要利用到simpson积分。首先可以看到,对于每一条路径都对应了一对参数(c,d),那么答案就是一下阴影部分面积

                                                                                                   2016年湖南省第十二届大学生计算机程序设计竞赛 解题报告_第2张图片

由于simpson积分对于次数不超过二的多项式能求出精确解,而且只需要左右端点和中点的函数值就可以求出积分值,考虑分治,对于一个区间,计算这个区间的积分值与左右两半的积分值的和,若相等,则说明这段区间是条直线,直接累加到答案上,若不相等,则继续分治下去。至于一个时间t的函数值,跑个最短路即可。

参考叉姐代码:点击打开链接


解法2:我的解法跟上面差不多,不过是从二分角度考虑。对于每一个位置t,我用二分求出这条线所能走到的位置,积分后累加到答案上

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 20 + 10;
 
struct edge {int to, c, d;};
typedef pair P;
 
std::vector g[maxn];
double dis[maxn];
PII cs[maxn];
int n, m, T;
 
PII dijkstra(int s, double t) {
    priority_queue, greater

>que; fill(dis, dis + n + 1, 1e20); dis[s] = 0; cs[s] = PII(0, 0); que.push(P(0, s)); while (!que.empty()) { P p = que.top(); que.pop(); int v = p.second; if (dis[v] < p.first) continue; for (int i = 0; i < g[v].size(); i++) { edge e = g[v][i]; if (dis[e.to] > dis[v] + e.c * t + e.d) { dis[e.to] = dis[v] + e.c * t + e.d; cs[e.to] = PII(cs[v].fi + e.c, cs[v].se + e.d); que.push(P(dis[e.to], e.to)); } } } //printf("%f %f %d %d\n", dis[n], t, cs[n].fi, cs[n].se); return cs[n]; } double simpson(double a, double b, int c, int d) { return (b - a) / 6 * (c * a + d + 4 * (c * ((a + b) / 2) + d) + c * b + d); } int main() { #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif while (~scanf("%d%d%d", &n, &m, &T)) { for (int i = 0; i <= n; i++) g[i].clear(); for (int i = 0; i < m; ++i) { int u, v, c, d; scanf("%d%d%d%d", &u, &v, &c, &d); g[u].push_back((edge) {v, c, d}); } double ans = 0; double curT = 0; PII cd = PII(0, 0); while (curT < T) { double l = curT, r = T + 1; PII auxcd=cd; while (r - l > eps) { double mid = (l + r) / 2; PII midcd = dijkstra(1, mid); if (midcd.fi == cd.fi && midcd.se == cd.se) l = mid; else { r = mid; auxcd = midcd; } } if (r > T) r = T; //printf("%f ", r); ans += simpson(curT, r, cd.fi, cd.se); curT = r; cd = auxcd; } printf("%.8f\n", ans / T); } }



Problem E:最长上升子序列~

原以为是dp,结果硬生生的变成了大模拟/(ㄒoㄒ)/~~

最后要求最长上升子序列的长度要恰好等于(n-1),可以想象到其样子:在一个完全升序的串取其中一个数K放到其他地方去,其余保持升序,这样就变成了长度恰好为(n-1)的串了。由于某些位置上的值已给定,那么这时候就需要对于每一个"非0的值"进行分类讨论:

1、若所有的a[i]==i,即每个值都在他对应的位置上,对于一段连续的0的区间,若K在这里面,那么一共有(区间长度-1)^2种方法使其变成上升子序列为(n-1)的串,把每个连续的0区间累加起来便是答案。

2、若max( abs(a[i]-1) )==1,即所有的值最多偏移一位的时候,若存在两种偏移方向(即a[i]==i-1和a[i]==i+1同时存在),那么当且仅当这两个值相邻,其余非零值在原位上,答案为1,否则为0。 若只有一种偏移方向,那么不在原位上的值应该是连续的,那么答案为ans(这个自己算算把,好难表达。。。)

3、若max( abs(a[i]-1 )>=2,若存在多个偏移量大于1的值,答案为0;若只有一个,那么在那个值的原位和现在的位置中间的值应该都相应的偏移1位,其余部分的值应该在原位,若满足,答案为1,若不满足,答案为0;

注意上述的值都只算非0的,对于0直接无视。


总之好麻烦,各种分支各种细节。。。

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 100000 + 10;
 
int n;
int a[maxn];
bool judge0() {
    for (int i = 1; i <= n; ++i) {
        if (a[i] != i && a[i] != 0) return false;
    }
    return true;
}
 
bool judge1(int &l, int &r, int &pe) {
    int flag = 0;
    pe = 0;
     
    for (int i = 1; i <= n; ++i) {
        if (a[i] == 0) continue;
        if (a[i] != i) {
            r = i;
            if (flag == 0) {
                pe = a[i] - i;
                l = i;
                flag = 1;
            } else if (flag == 1) {
                if (a[i] - i != pe) return false;
            } else if (flag == 2) {
                return false;
            }
        } else {
            if (flag == 1) flag = 2;
        }
    }
    return true;
}
 
bool judge2() {
    int p = -1;
    for (int i = 1; i <= n; ++i) {
        if (a[i] == 0) continue;
         
        if (abs(a[i] - i) >= 2) {
            if (p == -1) p = i;
            else return false;
        }
    }
     
    if (p == -1) {
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (a[i] != i && a[i] != 0) {
                if (a[i] == i + 1 && a[i + 1] == i) cnt++, i++;
                else return false;
            }
        }
        if (cnt != 1) return false;
    } else {
        if (a[p] > p) {
            int l = p, r = a[p];
            for (int i = 1; i <= n; i++) {
                if (a[i] == 0) continue;
                if ((i < l || i > r) && a[i] != i) return false;
                if ((i > l && i <= r) && a[i] != i - 1) return false;
            }
        } else {
            int l = a[p], r = p;
            for (int i = 1; i <= n; i++) {
                if (a[i] == 0) continue;
                if ((i < l || i > r) && a[i] != i) return false;
                if ((i >= l && i < r) && a[i] != i + 1) return false;
            }
        }
    }
     
    return true;
}
 
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
#endif
     
    while (~scanf("%d", &n)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
        }
         
        ll ans = 0;
        int l, r, pe;
        if (judge0()) {
            ll sum = 0;
            for (int i = 1; i <= n; i++) {
                if (a[i] == 0) sum++;
                else {
                    if (sum) {
                        ans += (sum - 1) * (sum - 1);
                        sum = 0;
                    }
                }
            }
            if (sum) ans += (sum - 1) * (sum - 1);
        } else if (judge2()) {
            ans = 1;
        } else if (judge1(l, r, pe)) {
            ll zero1 = 0, zero2 = 0;
             
            for (int i = l - 1; i >= 1 && a[i] == 0; i--) zero1++;
            for (int i = r + 1; i <= n && a[i] == 0; i++) zero2++;
             
            ans = (pe == -1 ? (zero2 + 1) * zero1 : (zero1 + 1) * zero2);
        }
        printf("%lld\n", ans);
    }
}

Problem F:地铁

这题相当有意思,解题思路完完全全就跟搭地铁一样。把每个点拆成多个点,每个新点对应一条地铁线路,换乘的花费就转换成了新点与新点上的边的花费,画个图秒懂:

                                                         2016年湖南省第十二届大学生计算机程序设计竞赛 解题报告_第3张图片

上面的1,2,3是指线路

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 100000 + 10;
 
struct edge {int to, c, t;};
int n, m;
vector g[maxn];
vector xl[maxn];
 
vector ng[maxn * 10];
map mp;
int mark;
 
int myhash(int i, int j) {
    return mp[PII(i, j)];
}
 
ll dis[maxn * 10];
priority_queue, greater > que;
ll spfa() {
    fill(dis, dis + mark, (ll)inf * inf);
     
    for (int i = 0; i < xl[1].size(); i++) {
        dis[myhash(1, xl[1][i])] = 0;
        que.push(PII(0, myhash(1, xl[1][i])));
    }
     
    while (!que.empty()) {
        PII p = que.top(); que.pop();
        int u = p.se;
        if (dis[u] < p.fi) continue;
         
        for (int i = 0; i < ng[u].size(); ++i) {
            int v = ng[u][i].fi;
            if (dis[v] > dis[u] + ng[u][i].se) {
                dis[v] = dis[u] + ng[u][i].se;
                que.push(PII(dis[v], v));
            }
        }
    }
     
    ll mx = (ll)inf * inf;
    for (int i = 0; i < xl[n].size(); ++i) {
        mx = min(mx, dis[myhash(n, xl[n][i])]);
    }
    return mx;
}
 
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
#endif
     
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 0; i <= n; ++i) {
            g[i].clear();
            xl[i].clear();
        }
        mp.clear();
         
        for (int i = 0; i < m; ++i) {
            int a, b, c, t;
            scanf("%d%d%d%d", &a, &b, &c, &t);
            g[a].push_back((edge) {b, c, t});
            g[b].push_back((edge) {a, c, t});
             
            xl[a].push_back(c);
            xl[b].push_back(c);
        }
         
        mark = 0;
        for (int i = 1; i <= n; i++) {
            sort(xl[i].begin(), xl[i].end());
            int tot = unique(xl[i].begin(), xl[i].end()) - xl[i].begin();
            xl[i].erase(xl[i].begin() + tot, xl[i].end());
             
            for (int j = 0; j < tot; ++j) {
                ng[mark].clear();
                mp[PII(i, xl[i][j])] = mark++;
            }
             
            for (int j = 0; j < tot - 1; ++j) {
                ng[myhash(i, xl[i][j])].push_back(PII(myhash(i, xl[i][j + 1]), xl[i][j + 1] - xl[i][j]));
                ng[myhash(i, xl[i][j + 1])].push_back(PII(myhash(i, xl[i][j]), xl[i][j + 1] - xl[i][j]));
            }
        }
         
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < g[i].size(); ++j) {
                int v = g[i][j].to;
                int c = g[i][j].c;
                ng[myhash(i, c)].push_back(PII(myhash(v, c) , g[i][j].t));
            }
        }
         
        printf("%lld\n", spfa());
    }
}


Problem G:Parenthesis

水题,把'('看成1,把')'看成-1,那么这个串合法当且仅当所有的前缀和为非负整数。然后交换的时候若左边是'('右边是')',则会使该区间内所有前缀和减2,所以在这种情况下看该区间的最小值是否>=2即可。其余交换方式直接'yes'


Problem H:Reverse

数学公式推导题。每个d[i]对最后答案的贡献分两种,一种是交换区间(l,r)不包含i,这种好算;另一种(l,r)包含i,这个时候我们可以找到这么个规律,当我们固定r,枚举l的时候,对于所有包含i的区间,d[i]能交换到的位置是已r为右边界,长度为i的连续区间,然后提提公因数,算算等比数列等差数列答案就出来了。


Problem I:Tree Intersection

还是先转一个别人的题解:点击打开链接

这位大牛已经说的好了,dfs序是个好东西,他往往能将树转换成数列,然后在数列上进行操作。

同时这题还能用启发式合并去做,先转一个ICPCCAMP里对启发式合并的讨论:点击打开链接

这可真是个神奇的东西,只需要每次合并的时候把小的集合合并到大的集合,时间复杂度就能神奇的变成nlogn。

由于上面大牛的启发式合并用的是主席树和splay树,作为一个弱逼并不太会,只好用map来进行启发式合并,效率大概比主席树慢了6倍左右

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 100000 + 10;
 
int colorx[maxn], colorsum[maxn];
std::vector g[maxn];
std::map mp;
int ans[maxn];
 
std::map merge(std::map a, std::map b) {
    if (a.size() < b.size()) {
        swap(a, b);
    }
     
    for (std::map::iterator it = b.begin(); it != b.end(); it++) {
        a[it->fi] += it->se;
    }
    return a;
}
 
std::map dfs(int u, int p) {
    std::map s;
    s[colorx[u]]++;
     
    for (int i = 0; i < g[u].size(); ++i) {
        int v = g[u][i];
        if (v != p) {
            std::map ss = dfs(v, u);
            s = merge(s, ss);
        }
    }
     
    int e = mp[PII(u, p)];
    for (std::map::iterator it = s.begin(); it != s.end(); ) {
        if (colorsum[it->fi] > it->se) {
            ans[e]++;
            ++it;
        } else {
            s.erase(it++);
        }
    }
    return s;
}
 
 
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
#endif
     
    int n;
    while (~scanf("%d", &n)) {
        memset(colorx, 0, sizeof colorx);
        memset(colorsum, 0, sizeof colorsum);
        memset(ans, 0, sizeof ans);
        for (int i = 0; i <= n; i++) g[i].clear();
        mp.clear();
         
        for (int i = 1; i <= n; ++i) {
            int c;
            scanf("%d", &c);
            colorx[i] = c;
            colorsum[c]++;
        }
         
        for (int i = 0; i < n - 1; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
            mp[PII(u, v)] = i;
            mp[PII(v, u)] = i;
        }
        mp[PII(1, 0)] = n - 1;
         
        dfs(1, 0);
        for (int i = 0; i < n - 1; ++i) {
            printf("%d\n", ans[i]);
        }
    }
}


Problem J:三角形和矩形

完全不会计算几何,已机智的扔给队友。水题,各种体位都能过。


Problem K:盖房子

像这种考虑两个东西组合的时候,我们往往需要先考虑只需要盖一个房子的时候。那么这样问题就变成了在这么个有障碍的地图上有多少个合法矩形,刘汝佳紫书上有个类似的问题,我们可以利用单调栈求得对于每个位置,以他为左上,左下,右上,右下角的时候的合法矩形数,然后我们现在需要利用这些信息去求两个不相交的矩形方案数。

以点(i,j)为右下角画矩形,为了保证不相交我们只能在除去(1,1)到(i,j)矩形上的其余位置当左上角做矩形,把两者数量相乘。然后发现,大部分矩形对都只算了一次,唯有一个矩形完全在另一个矩形的右上方或完全在左下方的时候重算了一次。所以这个时候我们只需要减去这种情况即可。表达不清,详见代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair PII;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 1111 + 10;
 
int n, m;
char g[maxn][maxn];
 
ll lu[maxn][maxn], ld[maxn][maxn], ru[maxn][maxn], rd[maxn][maxn];
ll sum_lu[maxn][maxn], sum_ld[maxn][maxn], sum_ru[maxn][maxn], sum_rd[maxn][maxn];
 
int high[maxn];
int que[maxn];
int tot;
 
void get_lu() {
    memset(lu, 0, sizeof lu);
    memset(high, 0, sizeof high);
     
    for (int i = n; i >= 1; i--) {
        tot = 0;
        que[tot++] = m + 1;
         
        for (int j = m; j >= 1; j--) {
            high[j]++;
            if (g[i][j] == '1') {
                high[j] = 0;
            }
             
            while (tot > 0 && high[j] <= high[que[tot - 1]]) tot--;
            que[tot++] = j;
             
            for (int k = tot - 1; k > 0; k--) {
                lu[i][j] += abs(que[k] - que[k - 1]) * high[que[k]];
            }
        }
         
    }
}
void get_ld() {
    memset(ld, 0, sizeof ld);
    memset(high, 0, sizeof high);
     
    for (int i = 1; i <= n; i++) {
        tot = 0;
        que[tot++] = m + 1;
         
        for (int j = m; j >= 1; j--) {
            high[j]++;
            if (g[i][j] == '1') {
                high[j] = 0;
            }
             
            while (tot > 0 && high[j] <= high[que[tot - 1]]) tot--;
            que[tot++] = j;
             
            for (int k = tot - 1; k > 0; k--) {
                ld[i][j] += abs(que[k] - que[k - 1]) * high[que[k]];
            }
        }
    }
}
void get_ru() {
    memset(ru, 0, sizeof ru);
    memset(high, 0, sizeof high);
     
    for (int i = n; i >= 1; i--) {
        tot = 0;
        que[tot++] = 0;
         
        for (int j = 1; j <= m; j++) {
            high[j]++;
            if (g[i][j] == '1') {
                high[j] = 0;
            }
             
            while (tot > 0 && high[j] <= high[que[tot - 1]]) tot--;
            que[tot++] = j;
             
            for (int k = tot - 1; k > 0; k--) {
                ru[i][j] += abs(que[k] - que[k - 1]) * high[que[k]];
            }
        }
    }
}
void get_rd() {
    memset(rd, 0, sizeof rd);
    memset(high, 0, sizeof high);
     
    for (int i = 1; i <= n; i++) {
        tot = 0;
        que[tot++] = 0;
         
        for (int j = 1; j <= m; j++) {
            high[j]++;
            if (g[i][j] == '1') {
                high[j] = 0;
            }
             
            while (tot > 0 && high[j] <= high[que[tot - 1]]) tot--;
            que[tot++] = j;
             
            for (int k = tot - 1; k > 0; k--) {
                rd[i][j] += abs(que[k] - que[k - 1]) * high[que[k]];
            }
        }
    }
}
void get_sum() {
    memset(sum_lu, 0, sizeof sum_lu);
    memset(sum_ld, 0, sizeof sum_ld);
    memset(sum_ru, 0, sizeof sum_ru);
    memset(sum_rd, 0, sizeof sum_rd);
     
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            sum_lu[i][j] = (sum_lu[i - 1][j] + sum_lu[i][j - 1] - sum_lu[i - 1][j - 1] + lu[i][j]) % mod;
            sum_ld[i][j] = (sum_ld[i - 1][j] + sum_ld[i][j - 1] - sum_ld[i - 1][j - 1] + ld[i][j]) % mod;
            sum_ru[i][j] = (sum_ru[i - 1][j] + sum_ru[i][j - 1] - sum_ru[i - 1][j - 1] + ru[i][j]) % mod;
            sum_rd[i][j] = (sum_rd[i - 1][j] + sum_rd[i][j - 1] - sum_rd[i - 1][j - 1] + rd[i][j]) % mod;
        }
    }
}
 
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
#endif
     
     
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%s", g[i] + 1);
        }
         
        get_lu();
        get_ld();
        get_ru();
        get_rd();
        get_sum();
         
        /*for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                printf("%d ", ru[i][j]);
            }
            puts("");
        }*/
         
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                ans += rd[i][j] * (sum_lu[n][m] - sum_lu[i][j]) % mod;
                ans -= ru[i][j] * (sum_ld[i - 1][m] - sum_ld[i - 1][j]) % mod;
                ans %= mod;
            }
        }
        printf("%lld\n", (ans + mod) % mod);
    }
}


总的来说,叉姐的题目质量还是非常高的,学习价值很大,A上一遍很有必要( •̀ ω •́ )y

你可能感兴趣的:(ACM)