10.03模拟总结
国庆模拟真愉快,真愉快!这只是个开始,之后我们还有4天连续的国庆模拟呢!而且可能不会像今天这么好改了orz。
来吧,我们来看看这些题。
T1.matrix
这道题考试的时候只想到了60分的暴力维护……就是只开一维差分,另一维直接暴力去修改……,不过这样的复杂度是O(np)的,这个肯定会超时。
然后大致想到了使用二维差分去维护,不过不知道怎么实现。(知道查询是可以用前缀和查询的,但是修改怎么修啊orz)
考试结束后听bin哥一讲就明白了,我们只要在右上和左下+1,左下和右上-1即可,这样的话我们就能保证原数组就是差分数组的前缀和。(只要简单想想就能知道啦)
之后我们再跑一遍,维护一次前缀和。
查询的适合只要用四个矩形互相加减即可,老套路了。
来看一下满分的代码。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') #define de putchar('#') #define pr pair<int,int> #define mp make_pair #define fi first #define sc second using namespace std; typedef long long ll; const int M = 100005; const int N = 2005; const int INF = 1000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >='0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } int n,m,p,q,dx,dy,sx,sy,c[N][N]; ll a[N][N]; void build() { rep(q,1,2) { rep(i,1,n-1) rep(j,1,m) a[i+1][j] += a[i][j]; rep(i,1,n) rep(j,1,m-1) a[i][j+1] += a[i][j]; } } int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); n = read(),m = read(),p = read(),q = read(); rep(i,1,p) { dx = read(),dy = read(),sx = read(),sy = read(); a[dx][dy]++,a[dx][sy+1]--,a[sx+1][dy]--,a[sx+1][sy+1]++; } build(); rep(i,1,q) { dx = read(),dy = read(),sx = read(),sy = read(); ll g = a[sx][sy] + a[dx-1][dy-1] - a[sx][dy-1] - a[dx-1][sy]; printf("%lld\n",g); } return 0; }
T2.card
好吧说实话第一眼我以为这是个组合题,还以为这是个与HNOI2008那道组合很相像的一道题。不过那道题要求维护的是相同的,那个其实特别好计算,而这道题是要求维护的是相邻两个数只和不能等于k,这个就很麻烦。
我一开始想能不能用组合的方法解决……好吧显然不行。这玩意咋组合啊……反正我是想不出来。想了一段时间之后我觉得换个思路,用递推的方式来做。我只考虑了k < m的情况,设f[i]表示第i个数选择了1~k-1范围之内的数的方案数,g[i]表示第i个数选择了k~m范围内的数的方案树。
于是乎我们有如下转移:
f[i] = f[i-1] * (k-2) + g[i-1] * (k-1) (还有取模)
g[i] = (g[i-1] + f[i-1]) * (m-k+1) (取模)
然后我就这么傻不拉几的交了上去,还得了50pts。
其实我考试结束之前10分钟突然发现这玩意可以用矩阵加速……然后我没时间写了。
哦,对于k > m的情况,一开始我听了ssy神犇的讲述……不过后来觉得好像还是自己想好懂一些。
其实完全可以照着k < m去推的,这次令f[i]表示选取1~k-m-1的情况的方案数,g[i]表示选择k-m~m的情况的方案数。于是得到以下方程:
f[i] = (f[i-1] + g[i-1])* (k-m-1);
g[i] = f[i-1] * (2m-k+1) + g[i-1] * (2m-k)
然后这玩意也可以用矩阵加速!然后就可以A了!
看一下满分代码。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') #define de putchar('#') #define pr pair<int,int> #define mp make_pair #define fi first #define sc second using namespace std; typedef long long ll; const int M = 2000005; const ll mod = 1000000007; ll read() { ll ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >='0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } ll m,n,k,f[M],g[M],ans; struct matrix { ll h[3][3]; matrix() { memset(h,0,sizeof(h)); } friend matrix operator * (const matrix &a,const matrix &b) { matrix c; rep(i,1,2) rep(j,1,2) rep(k,1,2) c.h[i][j] += (a.h[i][k] * b.h[k][j]) % mod,c.h[i][j] %= mod; return c; } }a; matrix mpow(matrix a,ll b) { matrix p; rep(i,1,2) p.h[i][i] = 1; while(b) { if(b & 1) p = p * a; a = a * a; b >>= 1; } return p; } int main() { //freopen("card.in","r",stdin); //freopen("card.out","w",stdout); m = read(),n = read(),k = read(); if(k > m) { a.h[1][1] = (k-m-1) % mod,a.h[1][2] = (k-m-1) % mod; a.h[2][1] = (2*m-k+1) % mod,a.h[2][2] = (2*m-k) % mod; matrix d = mpow(a,n-1); f[1] = (k-m-1) % mod,g[1] = (2*m-k+1) % mod; f[2] = (f[1] * d.h[1][1]) % mod + (g[1] * d.h[1][2]) % mod,f[2] %= mod; g[2] = (f[1] * d.h[2][1]) % mod + (g[1] * d.h[2][2]) % mod,g[2] %= mod; printf("%lld\n",(f[2] + g[2]) % mod); return 0; } a.h[1][1] = (k-2) % mod,a.h[1][2] = (k-1) % mod; a.h[2][1] = (m-k+1) % mod,a.h[2][2] = (m-k+1) % mod; matrix d = mpow(a,n-1); f[1] = (k-1) % mod,g[1] = (m-k+1) % mod; f[2] = (f[1] * d.h[1][1]) % mod + (g[1] * d.h[1][2]) % mod,f[2] %= mod; g[2] = (f[1] * d.h[2][1]) % mod + (g[1] * d.h[2][2]) % mod,g[2] %= mod; printf("%lld\n",(f[2] + g[2]) % mod); return 0; }
T3.station
这题考试的时候我还是只能想到O(nq)模拟,就是维护一下前缀和然后硬算。
然后莫名其妙的爆零了orz……
后来发现,其实对于每个村庄的距离,他相对于上一个村庄的距离就是加上左边的前缀和,减去右边的前缀和,所以其实它是有一定的单调性的。所以其实它是可以使用树状数组维护的……这倒是学了一招,可以直接维护现在的人数总和,然后其实我们只要在树状数组上二分一个人数总和的一半即可,那个位置一定是最小值。
我们来看一下代码吧。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') #define de putchar('#') #define pr pair<int,int> #define mp make_pair #define fi first #define sc second #define lowbit(x) x & (-x) using namespace std; typedef long long ll; const int M = 300005; const ll INF = 1000000000009; const ll mod = 998244353; ll read() { ll ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >='0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } ll naive[M],n,q,a[M],p,b,ans,s,minn,mpos,w = 1; void add(ll x,ll y) { while(x < M) a[x] += y,x += lowbit(x); s += y; } int query(ll x) { int t = 0; ll cur = 0; for(int i = (1 << 17);i;i >>= 1) if(cur + a[t+i] <= x) cur += a[t += i]; return t + 1; } int main() { freopen("station.in","r",stdin); freopen("station.out","w",stdout); n = read(),p = read(); rep(i,1,n) b = read(),add(i,b); while(p--) { q = read(),b = read(),add(q,b); w *= 19260817ll,w %= mod; ans += w * query((s-1) >> 1),ans %= mod; } printf("%lld\n",ans); return 0; }