A 4765 : 分块
B 4766 : 矩阵树定理或找规律
C 4767 : 容斥+DP
感觉难度应该是A>B>C?
可惜周二晚上在训练,不然也许就能全A了……
A 4765 普通计算姬
分块分块再分块……
想了一想感觉没什么树上的算法能用,考虑分块乱搞。把操作分根号块,每次进入新块时重建一整棵树。那么一次询问至多只会对应根号个修改。对于每一个点x,记录从x到根的路径上经过了1~n的哪些点,这同样也可以分块,对于根号个修改点直接查贡献即可。因为分块了,查完之后还会有一些块外小点没统计到。因小点不超过根号个,对于这些小点就暴力查子树和,这需要一种修改
O(n√)
询问
O(1)
的数据结构,同样还是可以分块……总时间复杂度
O(nn√)
#include<cstdio>
#include<cstring>
#define S 900
#define N 200005
#define ll unsigned long long
#define R register
using namespace std;
namespace runzhe2000
{
inline int read()
{
R int r = 0; R char c = getchar();
for(; c < '0' || c > '9'; c = getchar());
for(; c >='0' && c <='9'; r = r*10+c-'0', c = getchar());
return r;
}
ll sum[N], big[N], small[N];
int n, m, ecnt, d[N], last[N], root, beg[N], end[N], timer, fa[N], cnt[N][N/S+10], q[N], qv[S+10], qtop, bel[N], rebeg[N];
struct edge{int next, to;}e[N<<1];
void addedge(int a, int b)
{
e[++ecnt] = (edge){last[a], b};
last[a] = ecnt;
}
struct block
{
ll big[N/S+10],small[N];
block(){memset(big,0,sizeof big); memset(small,0,sizeof small);}
void init()
{
memset(big,0,N<<3);
memset(small,0,N<<3);
}
void add(int x, int v)
{
for(int i = bel[x], ii = N/S+10; i < ii; i++)
big[i] += v;
for(int i = x, ii = S*bel[x]; i <= ii; i++)
small[i] += v;
}
ll query(int x)
{
if(!x) return 0;
return big[bel[x]-1] + small[x];
}
}blo;
void init_sum(int x)
{
sum[x] = d[x];
for(int i = last[x]; i; i = e[i].next)
{
int y = e[i].to;
if(y == fa[x]) continue;
init_sum(y);
sum[x] += sum[y];
}
}
void dfs(int x)
{
rebeg[beg[x] = ++timer] = x;
memcpy(cnt[x], cnt[fa[x]], (N/S+10)<<2);
for(int i = bel[x], ii = N/S+10; i < ii; i++) cnt[x][i]++;
for(int i = last[x]; i; i = e[i].next)
{
int y = e[i].to;
if(y == fa[x]) continue;
fa[y] = x; dfs(y);
}
end[x] = timer;
}
ll calc(int lim)
{
if(!lim) return 0;
R ll ret = sum[lim];
for(R int i = 1; i <= qtop; i++)
ret += (ll)cnt[q[i]][bel[lim]-1] * qv[i];
for(int i = S*(bel[lim]-1)+1; i <= lim; i++)
ret += blo.query(end[i]) - blo.query(beg[i]-1);
return ret;
}
void main()
{
n = read(); m = read();
for(int i = 1; i <= n; i++)
{
d[i] = read();
bel[i] = bel[i-1] + (i%S==1?1:0);
}
for(int i = 1, a, b; i <= n; i++)
{
a = read(); b = read();
if(!a) {root = b; continue;}
if(!b) {root = a; continue;}
addedge(a, b);
addedge(b, a);
}
dfs(root);
blo.init();
for(R int i = 1, op, u, v; i <= m; i++)
{
op = read(), u = read(), v = read();
if(i % S == 1)
{
blo.init();
memset(sum,0,N<<3);
init_sum(root);
for(R int i = 1; i <= n; i++) sum[i] += sum[i-1];
qtop = 0;
}
if(op == 1)
{
q[++qtop] = u; qv[qtop] = v - d[u];
blo.add(beg[u], v - d[u]);
d[u] = v;
}
else
printf("%llu\n",calc(v) - calc(u-1));
}
}
}
int main()
{
runzhe2000::main();
}
B 4766 文艺计算姬
考场上打表找出规律,答案为 nm−1∗mn−1 。然后CJK学长教我用矩阵树定理直接推出来,orz orz orz。这里画矩阵不方便,就不写过程了。
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
namespace runzhe2000
{
ll n, m, p;
ll mul(ll a, ll b)
{
ll r = 0;
for(; a; a >>= 1)
{
if(a & 1) r = (r + b) % p;
b = (b + b) % p;
}
return r;
}
ll fpow(ll a, ll b)
{
ll r = 1;
for(; b; b >>= 1)
{
if(b&1) r = mul(r, a);
a = mul(a, a);
}
return r;
}
void main()
{
scanf("%lld%lld%lld",&n,&m,&p);
printf("%lld\n",mul(fpow(n,m-1),fpow(m,n-1)));
}
}
int main()
{
runzhe2000::main();
}
C 4767 两双手
把基底变成(0,1)(1,0),点坐标就好多了。显然容斥,记f[i][0/1]表示此时在i,已经至少经过的点数为偶数/奇数个时的方案数。由于图是拓扑图,可以直接DP出来。
#include<map>
#include<cstdio>
#include<algorithm>
#define mkp(u,v) make_pair(u,v)
#define N 505
#define M 666666
#define MOD 1000000007
using namespace std;
namespace runzhe2000
{
typedef long long ll;
const double eps = 1e-3;
struct point{int x, y;}p[N];
int Ex, Ey, Ax, Ay, Bx, By, n, ecnt, pcnt, f[N][2], fac[M], inv[M], inc[M], deg[N], q[N], last[N];
struct edge{int next, to;}e[N*N];
map<pair<int,int>,bool> vis;
void addedge(int a, int b)
{
e[++ecnt] = (edge){last[a], b};
last[a] = ecnt;
}
bool addpoint(int x, int y)
{
int nx = By ?
((double)y * Bx / By - x) / ((double)Ay * Bx / By - Ax) + eps:
((double)x * By / Bx - y) / ((double)Ax * By / Bx - Ay) + eps;
int ny = Ax ?
((double)x * Ay / Ax - y) / ((double)Bx * Ay / Ax - By) + eps:
((double)y * Ax / Ay - x) / ((double)By * Ax / Ay - Bx) + eps;
if(x != nx * Ax + ny * Bx || y != nx * Ay + ny * By || nx < 0 || ny < 0 || vis[mkp(nx,ny)]) return false;
vis[mkp(nx,ny)] = 1;
p[++pcnt] = (point){nx, ny}; return true;
}
int C(int a, int b){return (ll)fac[a] * inc[b] % MOD * inc[a-b] % MOD;}
void main()
{
fac[0] = fac[1] = inv[1] = inc[1] = inc[0] = 1;
for(int i = 2; i < 666666; i++)
{
fac[i] = (ll)fac[i-1] * i % MOD;
inv[i] = (ll)(MOD - MOD/i) * inv[MOD % i] % MOD;
inc[i] = (ll)inc[i-1] * inv[i] % MOD;
}
scanf("%d%d%d%d%d%d%d",&Ex,&Ey,&n,&Ax,&Ay,&Bx,&By);
addpoint(0,0);
for(int i = 1, x, y; i <= n; i++)
{
scanf("%d%d",&x,&y);
if(x == 0 && y == 0){puts("0"); return;}
if(x == Ex && y == Ey){puts("0"); return;}
addpoint(x,y);
}
if(Ex == 0 && Ey == 0) {puts("1"); return;}
if(!addpoint(Ex,Ey)) {puts("0"); return;}
for(int i = 1; i <= pcnt; i++) for(int j = 1; j <= pcnt; j++) if(i != j && p[i].x <= p[j].x && p[i].y <= p[j].y) addedge(i, j), deg[j]++;
q[0] = 1; f[1][1] = 1;
for(int head=0, tail=1; head<tail; head++)
{
int x = q[head];
for(int i = last[x]; i; i = e[i].next)
{
int y = e[i].to, P = C(p[y].y - p[x].y + p[y].x - p[x].x, p[y].x - p[x].x);
(f[y][0] += (ll)f[x][1] * P % MOD) %= MOD;
(f[y][1] += (ll)f[x][0] * P % MOD) %= MOD;
if(!(--deg[y])) q[tail++] = y;
}
}
printf("%d\n",((f[pcnt][0] - f[pcnt][1]) % MOD + MOD) % MOD);
}
}
int main()
{
runzhe2000::main();
}