考场
T1 又是 sb 题
T2 T3 都像是 DP
T4 国赛 D1T1???想起 D1 下午爆出的 zhengrui 原题和考试描述中的 zr5
,仿佛明白了什么。。。
草稿纸上列了一下时间规划,7.30 开写
7.50 拍上 T1
8.30 写完 T4,过不了大样例,写暴力拍出小数据 gdb 了一下,9.00 过拍
思索了一下,决定先打暴力,9.30 写完 T2 T3 暴力
T2 \(n\le19\) 跑了 2.7s,感觉卡卡常能过(背景见 hz游记 ),于是大力卡常到 2.3s,然后卡不动了。。。略微想了一下 T3,感觉啥性质/DP 状态都想不出来,还是看 T2。
三元环叉掉了我能想到的所有状压 DP 方法,感觉数据范围是要严格 \(O(T2^n)\),但我连 \(O(Tn2^n)\) 都想不出来。。。尝试改了下写法,发现暴力转移和 dfs 跑的一样快,寄希望于评测机了。
感觉 T2 要用到竞赛图的性质,然而我直到今天才知道它的准确定义,最后直接开始 tetris。。。
res
rk5 100+40+20+100
rk1 szs 100+100+100+100
联考中第一位 AK 的神仙,Orz
打地鼠
枚举端点,二维前缀和,\(O(n^2)\)
考场代码
char readc() { char c=getchar(); while(!isdigit(c))c=getchar(); return c; }
const int N = 2e3+5;
int n,k,s[N][N];
int ans;
int query(int a,int b,int c,int d)
{ return s[c][d] - s[c][b-1] - s[a-1][d] + s[a-1][b-1]; }
signed main() {
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
read(n,k);
For(i,1,n) For(j,1,n) s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+readc()-'0';
For(i,1,n-k+1) For(j,1,n-k+1) ckmax(ans,query(i,j,i+k-1,j+k-1));
write(ans);
return iocl();
}
竞赛图
竞赛图的性质:缩点后形成的 DAG 是一条链
根据这个性质,可以得到每个不强联通的诱导子图,必然可以分成两个点集 \(s,t\),满足 \(s\) 强联通且 \(s,t\) 之间的连边都指向 \(t\)。考虑状压 DP,预处理出点集 \(s\) 中所有点出边终点的并集 \(to[s]\),那么 \(to[s]\) 及其子集与 \(s\) 的并集均不强联通。
code
const int N = 25;
int T,n,w[N];
int all,to[1<<N];
bool f[1<<N];
void solve() {
read(n); all = (1<<n--)-1;
to[0] = all;
For(i,0,n) {
w[i] = 0;
For(j,0,n) { int x; read(x); w[i] |= x<<j; }
to[1<<i] = w[i];
}
For(s,1,all) to[s] = to[s^(s&-s)] & to[s&-s];
For(s,0,all) f[s] = 1;
For(s,1,all) if( f[s] && f[s|to[s]] )
for(int t = to[s]; t; t = (t-1)&to[s]) f[s|t] = 0;
int ans = 0;
For(s,0,all) ans += f[s];
write(ans);
}
signed main() {
read(T);
while( T-- ) {
mem(to,0,all);
solve();
}
return iocl();
}
糖果
并不是很懂,简单写两句吧
考虑先 DP 出 \(c\) 所选数的方案数,再计算顺序、未选数的影响
设 \(f[i,j,k]\) 表示 \(a\) 选到 \(i\),\(b\) 选到 \(j\),\(c\) 有 \(k\) 个可选数的方案数,发现如果同时枚举 \(i,j\),复杂度无法接受,那就让 \(i,j\) 分别移动,改设 \(f\) 为当前移 \(a\),\(g[i,j,k]\) 为当前移 \(b\) 的方案数。
DP 时先按列枚举状态,再按行转移。
具体见代码
code
const int N = 405, mod = 1e9+7;
int n,a[N],b[N];
int m,pa[N],pb[N];
LL ans,f[N][N][N/3],g[N][N][N/3];
signed main() {
read(n); m = n/3;
For(i,1,n) read(a[i]), pa[a[i]] = i;
For(i,1,n) read(b[i]), pb[b[i]] = i;
f[1][1][0] = 1;
For(i,1,n) For(j,1,n) For(k,0,m) {
f[i][j][k] %= mod, g[i][j][k] %= mod;
if( pb[a[i]] < j ) f[i+1][j][k] += f[i][j][k]; // 不合法:光移不选
else {
g[i][j][k] += f[i][j][k]; // 更新第二行
if( k ) f[i+1][j][k-1] += f[i][j][k] * k;
// 更新第三行:有k个选择,选后候选集合-1
}
if( pa[b[j]] < i ) g[i][j+1][k] += g[i][j][k]; // 类似
else if( b[j] != a[i] ) {
if( k ) g[i][j+1][k-1] += g[i][j][k] * k;
f[i+1][j+1][k+1] += g[i][j][k];
// 更新下一列:候选集合多了一个数
}
}
For(i,1,n) ans += f[n+1][i][0] % mod; ans %= mod;
for(int i = n; i; i -= 3) ans = ans * (i-1) * (i-2) %mod;
/*
DP 时确定了c中m个数,但没确定顺序和其他数,此处考虑
对于选的m个数,每次一定放在空位的第一个,其他两个对应的数可以随意放在
其他空位中,第一次有(n-1)(n-2)个,第二次有(n-4)(n-5)......
*/
write(ans);
return iocl();
}
树
每次修改时在点上打时间戳,那么一条边是白边当且仅当它的两端都有时间戳且相等,树剖+线段树维护即可。
具体维护方式见《 [SDOI2011]染色》,其他做法见《[NOI2021] 轻重边》
考场代码
const int N = 3e5+5;
int n,q;
vector<int> to[N];
int ind,fa[N],dep[N],siz[N],son[N],top[N],dfn[N];
struct Q {
int sum,lc,rc;
Q(int sum=0,int lc=0,int rc=0):sum(sum),lc(lc),rc(rc){}
};
Q operator + (Q l,Q r)
{ return Q{l.sum+r.sum+(l.rc&&r.lc&&l.rc!=r.lc),l.lc,r.rc}; }
#define ls (u<<1)
#define rs (u<<1|1)
namespace seg {
struct Node { int l,r,cov; Q c; } t[N*4];
void up(int u) { t[u].c = t[ls].c + t[rs].c; }
void down(int u,int x)
{ t[u].c.lc = t[u].c.rc = t[u].cov = x, t[u].c.sum = 0; }
void down(int u)
{ if( t[u].cov ) down(ls,t[u].cov), down(rs,t[u].cov), t[u].cov = 0; }
void build(int u,int l,int r) {
t[u] = Node{l,r,0,Q{r-l,-l,-r}};
if( l == r ) return;
int mid = l+r>>1;
build(ls,l,mid), build(rs,mid+1,r);
}
void modify(int u,int l,int r,int x) {
if( l <= t[u].l && t[u].r <= r ) return down(u,x), void();
down(u);
if( l <= t[ls].r ) modify(ls,l,r,x);
if( t[rs].l <= r ) modify(rs,l,r,x);
up(u);
}
Q query(int u,int l,int r) {
if( l <= t[u].l && t[u].r <= r ) return t[u].c;
down(u);
if( r <= t[ls].r ) return query(ls,l,r);
if( t[rs].l <= l ) return query(rs,l,r);
return query(ls,l,r) + query(rs,l,r);
}
}
#undef ls
#undef rs
void dfs1(int u,int fa) {
::fa[u] = fa, dep[u] = dep[fa]+1, siz[u] = 1;
for(int v : to[u]) if( v != fa ) {
dfs1(v,u);
siz[u] += siz[v];
if( siz[v] > siz[son[u]] ) son[u] = v;
}
}
void dfs2(int u,int top) {
::top[u] = top, dfn[u] = ++ind;
if( son[u] ) dfs2(son[u],top);
for(int v : to[u]) if( v != fa[u] && v != son[u] ) dfs2(v,v);
}
void modify(int u,int v,int x) {
while( top[u] != top[v] ) {
if( dep[top[u]] < dep[top[v]] ) swap(u,v);
seg::modify(1,dfn[top[u]],dfn[u],x);
u = fa[top[u]];
}
if( dep[u] > dep[v] ) swap(u,v);
seg::modify(1,dfn[u],dfn[v],x);
}
int query(int u,int v) {
Q x,y;
while( top[u] != top[v] ) {
if( dep[top[u]] < dep[top[v]] ) swap(u,v), swap(x,y);
x = seg::query(1,dfn[top[u]],dfn[u]) + x;
u = fa[top[u]];
}
if( dep[u] > dep[v] ) swap(u,v), swap(x,y);
swap(x.lc,x.rc);
return ((x + seg::query(1,dfn[u],dfn[v])) + y).sum;
}
signed main() {
// freopen("d.in","r",stdin);
// freopen("d.out","w",stdout);
read(n);
For(i,1,n-1) { int x,y; read(x,y); to[x].pb(y), to[y].pb(x); }
dfs1(1,0), dfs2(1,1), seg::build(1,1,n);
read(q);
For(i,1,q) {
int op,x,y; read(op,x,y);
if( op == 1 ) modify(x,y,i);
else write(query(x,y));
}
return iocl();
}