6091. 【GDOI2019模拟2019.3.30】唐时月夜(线性变换 + 倒推)

Problem

https://jzoj.net/senior/#main/show/6091

给定矩阵 A i , j = F ( i − 1 ) m + j A_{i,j}=F_{(i-1)m+j} Ai,j=F(i1)m+j,对矩阵的子矩阵进行三种操作,左右倒置行,上下倒置列,矩阵倒置,每次操作的子矩阵必定包含上次操作的子矩阵,最后求 ∑ i = 1 n ∑ j = 1 m A i , j ∗ F ( i − 1 ) m + j \sum_{i=1}^n\sum_{j=1}^mA_{i,j}*F_{(i-1)m+j} i=1nj=1mAi,jF(i1)m+j

n , m ≤ 4 ∗ 1 0 3 n,m\le 4*10^3 n,m4103

Solution

考虑把操作反过来做,那么每次对于多出来那一部分矩阵,实际上是进行了一个后缀的变换操作。

考虑到变换操作可以表示成一个线性变换,只与常数 k k k,坐标 ( x , y ) (x,y) (x,y)有关,维护这个常数 k k k,以及对应 a x + b y ax+by ax+by的系数,然后两个线性变换可以进行合并,于是就可以倒推一遍做到 O ( n m + q ) O(nm+q) O(nm+q)了。

Code

#include <iostream>
#include <cstring>
#include <cstdio>

#define F(i,a,b) for (int i = a; i <= b; i ++)
#define get getchar()

const int N = 4 * 1e3 + 10, Q = 2e5 + 10, T = N * N;

using namespace std;

unsigned int Ans, f[T], A[N][N], B[N][N];
int Num, n,m,q, a,b, k1,k2, s1,s2, W,E, K1,K2, S1,S2, tmp;
int x1[Q], y1[Q], x2[Q], y2[Q], opt[Q]; bool bz[N][N];
struct node { int x, y; } TT[N][N];

void Re(int &x) {
	char c = get; x = 0; int t = 1;
	for (; !isdigit(c); c = get) t = (c == '-' ? - 1 : t);
	for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = get); x *= t;
}

void Change(int &sx, int &sy) {
	tmp = sx;
	sx = K1 + (E == 1 ? sx : sy) * S1;
	sy = K2 + (E == 1 ? sy : tmp) * S2;
}

void Doit(int x, int y) {
	if (bz[x][y]) return; bz[x][y] = 1;
	int xx = x, yy = y;
	Change(xx, yy), TT[x][y] = {xx, yy};
}

int main() {
	freopen("evernight.in","r",stdin);
	freopen("evernight.out","w",stdout);

	Re(Num), Re(n), Re(m), Re(q);
	Re(a), Re(b), scanf("%u", &f[0]);
	F(i, 1, n * m) f[i] = (a * f[i - 1] + b);
	F(i, 1, q) Re(opt[i]), Re(x1[i]), Re(y1[i]), Re(x2[i]), Re(y2[i]);
	F(i, 1, n) F(j, 1, m) A[i][j] = f[(i - 1) * m + j], TT[i][j] = {i, j};

	S1 = S2 = E = s1 = s2 = W = 1;
	for (int i = q, j; i; i = j - 1) {
		for (j = i - 1; j && x1[j]==x1[i] && y1[j]==y1[i] && x2[j]==x2[i] && y2[j]==y2[i]; j --); j ++;

		k1 = k2 = 0, s1 = s2 = W = 1;
		F(k, j, i)
			switch (opt[k]) {
				case 1 : {
					k2 = y1[k] + y2[k] - k2;
					s2 = - s2;
					break;
				}
				case 2 : {
					k1 = x1[k] + x2[k] - k1;
					s1 = - s1;
					break;
				}
				case 3 : {
					tmp = k1;
					k1 = x1[k] - y1[k] + k2;
					k2 = y1[k] - x1[k] + tmp;
					W = 1 - W;
					swap(s1, s2);
					break;
				}
			}

		if (!E)
			swap(k1, k2), swap(s1, s2);
		E = 1 - (E ^ W);
		K1 += k1 * S1;
		K2 += k2 * S2;
		S1 = S1 * s1;
		S2 = S2 * s2;

		if (j - 1) {
			F(y, y1[j], y2[j]) {
				F(x, x1[j], x1[j - 1] - 1)
					Doit(x, y);
				F(x, x2[j - 1] + 1, x2[j])
					Doit(x, y);
			}
			F(x, x1[j], x2[j]) {
				F(y, y1[j], y1[j - 1] - 1)
					Doit(x, y);
				F(y, y2[j - 1] + 1, y2[j])
					Doit(x, y);
			}
		}
		else
			F(x, x1[j], x2[j]) F(y, y1[j], y2[j])
					Doit(x, y);
	}

	F(i, 1, n) F(j, 1, m) B[TT[i][j].x][TT[i][j].y] = A[i][j];
	F(i, 1, n) F(j, 1, m)
		Ans += B[i][j] * f[(i - 1) * m + j];

	printf("%u", Ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值