2020集训队作业选做part2
ARC 096D
Solution
首先不难发现\(p_i\)构成了一个树形结构,可以转化为每个点的代价是子树里所有点的\(m_i\)之和,价值是子树的节点个数,\(1\)号点可以选任意个,其他点最多选\(d\)个,求最大价值。
先考虑一个贪心,记\(w_i\)为代价,\(v_i\)为价值,先把所有点按\(\frac{w}{v}\)从小到大排序,然后从前往后贪心选。这样贪心肯定是错的,不过我们发现,由于\(n\leq 50\),所以\(v_i\leq 50\)。对于按\(\frac{w}{v}\)排过序的数组,如果存在某个\(i<j\),且\(i\)还能选的次数大于等于\(50\),j已经选的次数大于等于\(50\),那么我们可以让\(i\)再选\(v_j\)次,让j选的次数减少\(v_i\)次,这样总的价值是不变的,但是代价减小了,所以一定不会存在这种情况。
因此按照\(\frac{w}{v}\)从小到大排序后,我们可以每个物品拿\(50\)个出来进行背包,剩下的一定是从前往后贪心的拿。
此时总价值不超过\(\mathcal O(n^3)\),于是再考虑以总价值为状态的多重背包,即设\(f[i][j]\)为考虑到第\(i\)个物品,总价值为\(j\)的最小体积。
可以用二进制拆分或单调队列优化,复杂度分别为\(\mathcal O(n^4\log n)\)和\(\mathcal O(n^4)\)
Code
int n,m,D,len,tot;
int head[MAXN],val[MAXN],ord[MAXN],up[MAXN];
ll ans;
ll w[MAXN],f[MAXM],W[MAXM],V[MAXM];
struct Edge{
int to,next;
} e[MAXN << 1];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
void dfs(int u){
val[u] = 1;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
dfs(v);
val[u] += val[v];
w[u] += w[v];
}
}
bool cmp(const int &x,const int &y){
return 1ll * w[x] * val[y] < 1ll * w[y] * val[x];
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d%d%lld",&n,&m,&D,&w[1]);
for(int i = 2,x;i <= n;i++){
scanf("%lld%d",&w[i],&x);
add_edge(x,i);
}
dfs(1);
for(int i = 1;i <= n;i++) ord[i] = i, up[i] = D;
up[1] = INF;
sort(ord + 1,ord + 1 + n,cmp);
int maxV = 0;
for(int i = 1;i <= n;i++){
int x = ord[i], d = min(n,up[x]);
maxV += d * val[x];
up[x] -= d;
ll a = 0,b = 0;
while(a + (1 << b) <= d){
a += (1 << b);
V[++tot] = (1 << b) * val[x];
W[tot] = (1 << b) * w[x];
b += 1;
}
if(d - a) V[++tot] = (d - a) * val[x], W[tot] = (d - a) * w[x];
}
memset(f,0x3f,sizeof(f));
f[0] = 0;
for(int i = 1;i <= tot;i++){
for(int j = maxV;j >= 0;j--)
checkMin(f[j + V[i]],f[j] + W[i]);
}
for(int i = 0;i <= maxV;i++){
if(f[i] <= m){
ll x = i,sum = f[i];
for(int j = 1;j <= n;j++){
int y = ord[j];
ll c = min((ll)up[y],(m - sum) / w[y]);
x += c * val[y];
sum += c * w[y];
}
ans = max(ans,x);
}
}
printf("%lld\n",ans);
return 0;
}
CF 576E
Solution
线段树分治,考虑对于一条边的两次染色\(x,y\),染色区间为 \([x+1,y-1]\),颜色有两种可能:
- 染上去了,则颜色是\(x\)染色时的颜色。
- 没染上去,则颜色是上一次染色的颜色。
那么判断变成一个单点判断了,分治到那个叶子的时候进行判断即可。
总时间复杂度\(\mathcal O(m \log n \log q)\)。
Code
int n,m,Q,K;
int nxt[MAXN],e[MAXN],c[MAXN],u[MAXN],v[MAXN],col[MAXN];
struct UnionSet{
int f[MAXN << 1],size[MAXN << 1];
void init(){
for(int i = 1;i <= n;i++){
f[i] = i;
f[i + n] = i + n;
size[i] = 1;
}
}
int find(int x){
if(x == f[x]) return x;
return find(f[x]);
}
pii merge(int x,int y){
x = find(x); y = find(y);
if(x == y) return make_pair(0,0);
if(size[x] > size[y]) swap(x,y);
size[y] += size[x]; f[x] = y;
return make_pair(x,y);
}
bool check(int x,int y){
return find(x) != find(y);
}
void undo(pii p){
int x = p.first,y = p.second;
size[y] -= size[x];
f[x] = x;
}
} S[MAXC];
struct SegmentTree{
vector<int> vec[MAXN << 2];
stack<piii> st;
#define lson k << 1
#define rson k << 1 | 1
void modify(int k,int l,int r,int ql,int qr,int x){
if(l >= ql && r <= qr){
vec[k].push_back(x);
return;
}
int mid = (l + r) >> 1;
if(ql <= mid) modify(lson,l,mid,ql,qr,x);
if(qr > mid) modify(rson,mid + 1,r,ql,qr,x);
}
void undo(int limit){
while(st.size() > limit){
piii p = st.top(); st.pop();
S[p.first].undo(p.second);
}
}
void query(int k,int l,int r){
int t = st.size();
for(int x : vec[k]){
int C = c[x]; x = e[x];
int a = u[x],b = v[x];
if(!C) continue;
pii p;
p = S[C].merge(a,b + n);
if(p.first) st.push(make_pair(C,p));
p = S[C].merge(a + n,b);
if(p.first) st.push(make_pair(C,p));
}
if(l == r){
int C = c[l],a = u[e[l]],b = v[e[l]];
if(S[C].check(a,b)) puts("YES"), col[e[l]] = C;
else puts("NO"), c[l] = col[e[l]];
return undo(t), void();
}
int mid = (l + r) >> 1;
query(lson,l,mid);
query(rson,mid + 1,r);
undo(t);
}
} T;
int main(){
scanf("%d%d%d%d",&n,&m,&K,&Q);
for(int i = 1;i <= K;i++) S[i].init();
for(int i = 1;i <= m;i++){
scanf("%d%d",&u[i],&v[i]);
nxt[i] = Q + 1;
}
for(int i = 1;i <= Q;i++)
scanf("%d%d",&e[i],&c[i]);
for(int i = Q;i >= 1;i--){
int x = e[i];
if(i + 1 < nxt[x])
T.modify(1,1,Q,i + 1,nxt[x] - 1,i);
nxt[x] = i;
}
T.query(1,1,Q);
return 0;
}
CF 704D
咕咕咕
AGC 027D
Solution
首先把网格黑白染色,那么相同颜色的点互不影响,如果我们令\(max \mod min=1\),且白点的权值都已经确定,那么黑点的权值可以设成它周围四个白点权值的\(lcm+1\),对于白点的权值,因为要使黑点的权值在\(10^{15}\)且所有数各不相同,所以可以对于每一条主对角线和每一条副对角线各给一个素数,一个白点的权值就是所在主对角线和副对角线的权值之积。
注意要特判\(n=2\)的情况。
Code
int n,tot;
int p[MAXM],nprime[MAXM];
ll a[MAXN][MAXN];
void Init(){
for(int i = 2;i <= M;i++){
if(!nprime[i]) p[++tot] = i;
for(int j = 1;j <= tot;j++){
if(i * p[j] > M) break;
nprime[i * p[j]] = 1;
if(i % p[j] == 0) break;
}
}
}
ll gcd(ll x,ll y){
if(!y) return x;
return gcd(y,x % y);
}
ll lcm(ll x,ll y){
if(!x || !y) return x + y;
return x / gcd(x,y) * y;
}
int main(){
scanf("%d",&n);
if(n == 2) return printf("4 7\n 23 10\n"), 0;
Init();
tot = 0;
for(int i = 2;i <= 2 * n;i += 2){ // x + y = i
int x = p[++tot];
for(int j = max(1,i - n);j <= min(n,i - 1);j++)
a[j][i - j] = x;
}
for(int i = (n & 1) ? 1 - n : 2 - n;i < n;i += 2){ // |x - y| = i
int x = p[++tot];
for(int j = 1;j <= n;j++){
if(i + j >= 1 && i + j <= n)
a[j][i + j] *= x;
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
if(!a[i][j]) a[i][j] = lcm(lcm(a[i - 1][j],a[i][j - 1]),lcm(a[i + 1][j],a[i][j + 1])) + 1;
printf("%lld ",a[i][j]);
assert(a[i][j] <= 1000000000000000ll);
}
printf("\n");
}
return 0;
}
AGC 030E
Solution
考虑在\(0\)与\(1\)间画一条红线,\(1\)与\(0\)间画一条蓝线,并且假设开头和结尾都有无线交替的红蓝线,那么两个串相等等价于它们对应的红蓝线相等。每次可以移动一条红线或蓝线,要保证移动后红蓝线交替出现且相邻的红蓝线距离为\(1\)或\(2\),具体如下图。

假设我们已经确定了\(s\)中的红线和\(t\)中的蓝线的对应关系,显然答案的下界是对应线段的距离之和,在下图中答案为\(\cdots +0+0+0+1+0+1+2+3+2+1+0\cdots=10\)

这个下界也是能达到的,因为要求相邻线之间的距离不超过\(2\),所以向右移的线和向左移动的线不能相邻,那么就相当于保持不动的线将所有线分成了若干部分,每一部分的线都是向同一个方向移动,显然在每一部分内一定存在一种移动方案。
由于本质不同的安排对应线段的方案只有\(\mathcal O(n)\)种,所以可以枚举对应方案,计算答案即可,时间复杂度\(\mathcal O(n^2)\)
Code
int n,tot1,tot2,ans = INF;
int a[MAXN],b[MAXN];
char s[MAXN],t[MAXN];
int main(){
scanf("%d%s%s",&n,s + 1,t + 1);
for(int i = 1;i < n;i++){
if(s[i] != s[i + 1]) a[++tot1] = i;
}
for(int i = 1;i < n;i++){
if(t[i] != t[i + 1]) b[++tot2] = i;
}
for(int i = -tot1 - 1;i <= tot2 + 1;i++){
if((i & 1) ^ (s[1] == t[1])){
int sum = 0;
for(int j = min(1,1 - i);j <= max(tot1,tot2 - i);j++)
sum += abs((j < 0 ? 0 : (j <= tot1 ? a[j] : n)) - (i + j < 0 ? 0 : (i + j <= tot2 ? b[i + j] : n)));
ans = min(ans,sum);
}
}
printf("%d\n",ans);
return 0;
}
CF 516E
Solution
首先,设\(d=\gcd(n,m)\),由简单的数论知识可知,在任意一天一起玩的男生和女生编号在\(\bmod \ d\)意义下同余。所以我们可以把所有男生和女生按模\(d\)的值分组,然后我们只考虑同一组内的情况。那么若求得模\(d\)余\(r\)的这一组子问题的答案为\(t\),那么在原问题中这组的答案即为\(dt+r\)(当第\(0\)天就合法时需要特判)。然后我们只需要对所有组的答案取\(max\)。
由裴蜀定理可知,只要一开始有一个人是快乐的,那么最后所有人都会变快乐。由于只有\(b+g\)个人快乐,所以当\(d>b+g\)时肯定至少有一组中一开始没有人快乐,输出\(-1\)即可。
现在只需要考虑\(\gcd(n,m)=1\)的情况。我们考虑分别算出最后一个男生和最后一个女生的变快乐的时间,然后取\(max\)即可。先考虑女生的情况,男生类似。
我们发现,如果在第\(i\)天第\(i\bmod m\)个女生是快乐的,那么第\(i\)天结束时,第\(i\bmod n\)个男生也一定是快乐的,在过了\(n\)天以后,第\(i\bmod n\)个男生会让第\((i+n)\bmod m\)个女生变快乐。
那么这可以等价于,\(n\)天以后,第\(i\bmod m\)个女生让第\((i+n)\bmod m\)个女生变快乐了。所以,如果第\(i\)个女生在某一天变快乐了,那么\(n\)天后,第\((i+n)\bmod m\)个女生也一定会快乐。这相当于一个同余最短路的模型。
-
对每个女生\(k\)我们向第\((k+n)\bmod m\)个女生连一条边权为\(n\)的边,这相当于某时刻\(k\)变得快乐,那么\(k+n\)时刻 \((k+n)\bmod m\)就会快乐。
-
对每个一开始就快乐的女生\(k\),从源点\(S\)向\(k\)连一条边权为\(k\)的边。
-
对每个一开始就快乐的男生\(k\),从\(S\)向\(k\bmod m\)连一条边权为\(k\)的边,表示时刻\(k\)第\(k\bmod m\)个女生会因该男生变快乐。
这样跑从\(S\)开始的最短路,求每个点的\(dis\)的\(max\)就是答案。
但是发现点数是\(10^9\)级别的,无法跑最短路。继续观察这个图,发现第一类边把所有除\(S\)以外的点练成了一个大环,且环上的边边权相同。称\(S\)直接连向的点为关键点,那么关键点的\(dis\)即为\(S\)连向这个点的边权。我们考虑把所有关键点按在环上的顺序排序(可以钦定一个点为起点,然后用\(exgcd\)算出它到其它所有点的步数),那么对于连续的两个关键点\(A,B\),显然\(B\)前面的那个点是\(AB\)这一段之间\(dis\)最大的,直接算出\(B\)前面的点的\(dis\)更新答案即可。
时间复杂度\(\mathcal O((b+g)\log n)\)
Code
int n,m,b,g,d;
int boy[MAXN],girl[MAXN];
ll ans;
vector<int> B[MAXN],G[MAXN];
vector<tuple<int,int,int> > vec;
map<int,int> mp;
int gcd(int x,int y){
if(!y) return x;
return gcd(y,x % y);
}
int exgcd(int a,int b,int &x,int &y){
if(!b) return x = 1, y = 0, a;
int g = exgcd(b,a % b,y,x);
return y -= (a / b) * x, g;
}
ll Solve(const vector<int> &va,const vector<int> &vb){
ll ans = 0; mp.clear(); vec.clear();
for(int x : vb) mp[x] = x;
for(int x : va){
if(mp.count(x % m)) checkMin(mp[x % m],x);
else mp[x % m] = x;
}
for(auto p : mp){
int x,y;
exgcd(n,m,x,y);
x = (x % m + m) % m;
int t = 1ll * x * p.first % m;
vec.push_back(make_tuple(t,p.first,p.second));
}
sort(vec.begin(),vec.end());
ll res = 0;
if(vec.size() > 1){
for(int i = 1;i < (int)vec.size();i++)
checkMax(res,get<2>(vec[i - 1]) + 1ll * (get<0>(vec[i]) - get<0>(vec[i - 1]) - 1) * n);
checkMax(res,get<2>(vec.back()) + 1ll * (m - get<0>(vec.back()) + get<0>(vec[0]) - 1) * n);
}else
res = 1ll * (m - 1) * n + get<2>(vec[0]);
return res;
}
int main(){
scanf("%d%d",&n,&m);
scanf("%d",&b);
for(int i = 1;i <= b;i++) scanf("%d",&boy[i]);
scanf("%d",&g);
for(int i = 1;i <= g;i++) scanf("%d",&girl[i]);
d = gcd(n,m);
if(d > b + g) return puts("-1"), 0;
for(int i = 1;i <= b;i++) B[boy[i] % d].push_back(boy[i] / d);
for(int i = 1;i <= g;i++) G[girl[i] % d].push_back(girl[i] / d);
n /= d; m /= d;
for(int i = 0;i < d;i++){
if(G[i].size() == m) continue;
if(G[i].empty() && B[i].empty()) return puts("-1"), 0;
checkMax(ans,Solve(B[i],G[i]) * d + i);
}
swap(n,m);
for(int i = 0;i < d;i++){
if(B[i].size() == m) continue;
if(G[i].empty() && B[i].empty()) return puts("-1"), 0;
checkMax(ans,Solve(G[i],B[i]) * d + i);
}
printf("%lld\n",ans);
return 0;
}
AGC 025D
Solution
首先只考虑一个\(D\)的情况,我们将所有距离为\(\sqrt D\)的点连边,得到的一定是一张二分图。证明:设\(D=4^k\cdot p(p \bmod 4 \neq 0)\),那么所有距离为\(D\)的点对都可以写成\((2^k\cdot a,2^k \cdot b)\)的形式,其中满足\(a^2+b^2=p\)。于是可以只考虑\(\bmod 2^k\)的等价类。
- \(p\bmod 4 \equiv 3\),此时不存在合法点对。
- \(p\bmod 4 \equiv 1\),此时\(a\)和\(b\)一奇一偶,按\(a+b\)的奇偶性黑白染色即可。
- \(p\bmod 4 \equiv 2\),此时\(a\)和\(b\)都为奇数,按\(a\)的奇偶性黑白染色即可。
由于一定能黑白染色,所以就证明了得到的图一定是二分图。
那么问题变为有两张\((2n)^2\)个点的二分图,要求取出一个大小为\(n^2\)的点集,使得这个点集在两张图上均为独立集。给两张图分别二染色后,共有四种颜色,考虑给每个格子赋上两个二分图中的各一种颜色,共有\(4\)中不同的颜色组合,根据鸽巢原理,一定能找到一种颜色组合满足这样的格子至少有\(n^2\)个。
时间复杂度\(\mathcal O(n^2)\)
Code
int n,D1,D2;
int col[3][MAXN][MAXN],cnt[2][2];
void Solve(int d,int id){
int k = 0;
while(d % 4 == 0) d /= 4, k += 1;
if(d % 4 == 3) return;
k = 1 << k;
if(d % 4 == 1){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++)
col[id][i][j] = ((i - 1) / k + (j - 1) / k) & 1;
}
}else{
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++)
col[id][i][j] = ((i - 1) / k) & 1;
}
}
}
int main(){
scanf("%d%d%d",&n,&D1,&D2);
n <<= 1;
Solve(D1,1);
Solve(D2,2);
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++)
cnt[col[1][i][j]][col[2][i][j]] += 1;
}
int x,y;
for(int i = 0;i < 2;i++){
for(int j = 0;j < 2;j++){
if(cnt[i][j] >= n * n / 4)
x = i, y = j;
}
}
int tot = 0;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
if(col[1][i][j] == x && col[2][i][j] == y){
printf("%d %d\n",i - 1,j - 1);
tot += 1;
if(tot >= n * n / 4) return 0;
}
}
}
return 0;
}
AGC 035E
Solution
考虑假如\(x\)与\(x-2\)最后都要被删除,肯定应该先删\(x\)再删\(x-2\),因为如果先删\(x-2\)的话,再删\(x\)就会又多出来一个\(x-2\))。
考虑建图,连边\(x \to x-2,x\to x+K\),分别表示\(x\)要比 \(x-2,x+K\)先删。最后如果要求删除的点形成一个环,肯定无解。否则我们按照拓扑序来删必然是一个合法方案。那么原问题相当于对于这样一个图,求有多少点集满足点集内的点不形成环。
考虑与\(K\)无关的那些边,会连成\(1 \gets 3 \gets \cdots\)与$2 \gets 4 \gets \cdots \(两条链,一条链上全是奇数,一条链上全是偶数。下面按\)K\(的奇偶性讨论,因为\)K\(是偶数时\)x \to x+K\(这样的边必然是在每条链内部连边,而\)K$为奇数时则是两条链之间的边。
-
\(K\)为偶数,图中只会有\(a \to a-2 \to ...\to a-K \to a\)这样的环,由于点集内不能形成环,这相当于每条链内不能连续选择超过\(K/2 + 1\)个点,简单dp即可。
-
\(K\)为奇数。注意到最小环必然恰好经过\(2\)条跨过链的边,考虑将图画成以下形式(下图中\(K = 3\))

即对于每个奇数\(x\),将\(x\)与\(x+K\)放在同一层,注意到环的形式一定为\(a \to a-2 \to \cdots \to b-K \to b \to b-2 \to \cdots \to a-K \to a\)(假设\(a\)为奇数),对应到图上即从\(a\)开始往上走到某一点\(b-K\),再往右走到 \(b\),再往上走到\(a-K\),最后回到\(a\)。这相当于一条往上、往右、往上的路径包含大于\(K + 1\)个点,就会形成环。
注意到这样一条路径没有往下的选择,所以我们就可以从上到下 dp。设\(f[i][j][k]\)表示前\(i\)层,往上、往右、往上的路径包含\(j\)个点,右边偶数的链对应往上的点连续选中了\(k\)个的方案数。设置\(k\)这一维是为了方便我们得到新的\(j\)(可能在\(i\)这个点直接往右走),只有\(j\leq K+1\)的状态合法,转移即可。
时间复杂度\(\mathcal O(n^2K)\)
Code
int n,K,MOD;
int f[MAXN][MAXN],g[MAXN << 1][MAXN][MAXN];
void Solve1(){
K /= 2; f[0][0] = 1;
for(int i = 1;i <= n;i++){
for(int j = 0;j <= K;j++)
addmod(f[i][0],f[i - 1][j]);
for(int j = 0;j < K;j++)
addmod(f[i][j + 1],f[i - 1][j]);
}
int sum1 = 0,sum2 = 0;
for(int i = 0;i <= K;i++)
addmod(sum1,f[n / 2][i]);
for(int i = 0;i <= K;i++)
addmod(sum2,f[(n + 1) / 2][i]);
printf("%lld\n",1ll * sum1 * sum2 % MOD);
}
void Solve2(){
g[0][0][0] = 1;
int cur = 0;
for(int i = 0;i <= n + K - 2;i += 2){
cur = i + 2;
for(int j = 0;j <= n;j++){
for(int k = 0;k <= K + 1;k++)
addmod(g[i + 2][0][0],g[i][j][k]);
}
if(i + 2 <= n){
for(int j = 0;j <= n;j++){
for(int k = 0;k <= K + 1;k++)
addmod(g[i + 2][j + 1][0],g[i][j][k]);
}
}
if(i + 2 >= K + 1){
for(int j = 0;j <= n;j++){
for(int k = 1;k <= K;k++)
addmod(g[i + 2][0][k + 1],g[i][j][k]);
addmod(g[i + 2][0][0],g[i][j][0]);
}
}
if(i + 2 >= K + 1 && i + 2 <= n){
for(int j = 0;j <= n;j++){
for(int k = 0;max(k,j + 1) <= K;k++)
addmod(g[i + 2][j + 1][max(k + 1,j + 2)],g[i][j][k]);
}
}
}
int sum = 0;
for(int j = 0;j <= n;j++){
for(int k = 0;k <= K + 1;k++)
addmod(sum,g[cur][j][k]);
}
printf("%d\n",sum);
}
int main(){
scanf("%d%d%d",&n,&K,&MOD);
if(K & 1) Solve2();
else Solve1();
return 0;
}
CF 578E
Solution
将\(L\)看作\(0\),\(R\)看作\(1\),那么本题等价于将\(s\)划分成若干个\(01\)交替的子序列,且子序列首尾拼接后仍是\(01\)交替出现的,要求最小化子序列个数。贪心即可,对于每个子序列尽可能多的加入字符,若无法加入字符则新开一个子序列即可。
然后考虑如何满足首尾拼接后仍是\(01\)交替出现的这个限制,将所有子序列按照开头和结尾划分为\(4\)类,分别为\(00,01,10,11\)。不难发现,在只有\(01\)和\(10\)的时候,是无法拼在一起的,其他情况都是可以拼接的。考虑如何处理这种情况,可以通过将\(01\)和\(10\)转化为\(00\)和\(11\)解决。具体来说,考虑\(01\)与\(10\)最后一个位置的大小关系,将最后一个位置较小的子序列的末尾给较大的那个子序列,那么\(01\)就变成了\(00\),\(10\)就变成了\(11\),可以拼在一起。
时间复杂度\(\mathcal O(n)\)
Code
int n,tot;
int a[MAXN],id[MAXN];
vector<int> pos[MAXN],vec[2][2],tmp[2];
char s[MAXN];
void print(const vector<int> &v){
for(int x : v){
for(int y : pos[x])
printf("%d ",y);
}
}
int main(){
scanf("%s",s + 1);
n = strlen(s + 1);
for(int i = 1;i <= n;i++){
a[i] = (s[i] == 'R');
if(tmp[a[i] ^ 1].empty())
id[i] = ++tot;
else
id[i] = tmp[a[i] ^ 1].back(), tmp[a[i] ^ 1].pop_back();
tmp[a[i]].push_back(id[i]);
pos[id[i]].push_back(i);
}
for(int i = 1;i <= tot;i++)
vec[a[pos[i][0]]][a[pos[i].back()]].push_back(i);
if(!vec[1][0].empty() && !vec[0][1].empty() && vec[0][0].empty() && vec[1][1].empty()){
int x = vec[1][0].back(), posx = pos[x].back();
int y = vec[0][1].back(), posy = pos[y].back();
int t = 0;
if(posx > posy)
swap(x,y), swap(posx,posy), t ^= 1;
pos[y].pop_back();
pos[x].push_back(posy);
vec[t][t ^ 1].pop_back();
vec[t ^ 1][t].pop_back();
vec[t ^ 1][t ^ 1].push_back(x);
vec[t][t].push_back(y);
}
int t = 0;
if(vec[1][1].empty()) t ^= 1;
printf("%d\n",tot - 1);
print(vec[t][t]);
print(vec[t ^ 1][t]);
print(vec[t ^ 1][t ^ 1]);
print(vec[t][t ^ 1]);
return 0;
}
ARC 099D
Solution
考虑构造一个类似生成函数或者哈希的东西\(f(x)=\sum_{i=-\infty}^{+\infty}A_ix^i\),再构造一个指针\(g\),开始指向\(0\)的位置,显然初始时\(f(x)=0\)。
设\(f_i(x)\)表示\(1\sim i\)这一段子串操作所得到的\(f(x)\)的值,\(g_i\)表示\(1\sim i\)操作后的指针位置。考虑在末尾加入一个字符\(c\),若\(c\)为\(+\)或\(-\),则
若\(c\)是\(>\)或\(<\),则
接下来考虑如何计算\([l,r]\)的\(f(x)\)的值,不难发现就是
最后要求的就是满足
的\((l,r)\)的个数 ,变形一下可得
随便取一个\(x\),枚举右端点,拿个map统计一下左端点的个数即可。
时间复杂度\(\mathcal O(n\log n)\)
Code
int n;
ll ans;
int f1[MAXN],g1[MAXN],f2[MAXN],g2[MAXN];
char s[MAXN];
map<pii,int> mp;
int main(){
scanf("%d",&n);
scanf("%s",s + 1);
g1[0] = g2[0] = 1;
for(int i = 1;i <= n;i++){
f1[i] = f1[i - 1]; f2[i] = f2[i - 1];
g1[i] = g1[i - 1]; g2[i] = g2[i - 1];
if(s[i] == '+')
addmod1(f1[i],g1[i]), addmod2(f2[i],g2[i]);
else if(s[i] == '-')
submod1(f1[i],g1[i]), submod2(f2[i],g2[i]);
else if(s[i] == '>')
g1[i] = 1ll * g1[i] * BASE1 % MOD1, g2[i] = 1ll * g2[i] * BASE2 % MOD2;
else if(s[i] == '<')
g1[i] = 1ll * g1[i] * IBASE1 % MOD1, g2[i] = 1ll * g2[i] * IBASE2 % MOD2;
}
mp[make_pair(f1[n],f2[n])] += 1;
for(int i = 1;i <= n;i++){
ans += mp[make_pair(f1[i],f2[i])];
mp[make_pair(add1(1ll * f1[n] * g1[i] % MOD1,f1[i]),add2(1ll * f2[n] * g2[i] % MOD2,f2[i]))] += 1;
}
printf("%lld\n",ans);
return 0;
}
CF 704B
Solution
考虑贪心,维护一个\(s\to e\)的链表,然后从小到大考虑\(1\)到\(n\)中每个不是\(s\)和\(e\)的元素,枚举它在链表中插入的位置,找到最优的位置插入即可。正确性的话感性理解一下:由于答案唯一,那么最后得到的链必定是唯一的,每个点所在的位置也一定是最短的。
时间复杂度\(\mathcal O(n^2)\)
Code
int n,s,e;
ll ans;
int a[MAXN],b[MAXN],c[MAXN],d[MAXN],nxt[MAXN],x[MAXN];
ll Calc(int i,int j){
if(i > j) return 0ll + x[i] - x[j] + c[i] + b[j];
else return 0ll + x[j] - x[i] + d[i] + a[j];
}
int main(){
scanf("%d%d%d",&n,&s,&e);
for(int i = 1;i <= n;i++) scanf("%d",&x[i]);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= n;i++) scanf("%d",&b[i]);
for(int i = 1;i <= n;i++) scanf("%d",&c[i]);
for(int i = 1;i <= n;i++) scanf("%d",&d[i]);
nxt[s] = e;
ans = Calc(s,e);
for(int i = 1;i <= n;i++){
if(i == s || i == e) continue;
pli p = make_pair(llINF,0);
for(int j = s;j != e;j = nxt[j])
checkMin(p,make_pair(Calc(j,i) + Calc(i,nxt[j]) - Calc(j,nxt[j]),j));
nxt[i] = nxt[p.second];
nxt[p.second ] = i;
ans += p.first;
}
printf("%lld\n",ans);
return 0;
}
CF 578F
Solution
首先把所有的格点黑白染色,那么镜子连接的点颜色一定相同,将一块镜子连接的两个点连边,那么最终黑点和白点各构成了一颗树,确切地说,一个合法的方案一定唯一对应一棵黑点或白点的生成树。
证明:首先第二个条件等价于无环,因为每一条光线都有唯一的前驱和后继,假设不存在从边界入射的光线可以穿过这条边,那么一定形成了一个光线构成的环。对于第一个条件,考虑边界上相邻的两个同色点,那么这两个点间对应的两条边的光线一定是按照两个点在树上的路径走的,且这两条边互相到达。
考虑如何求答案,对于所有已知的边,使用并查集缩起来,然后枚举黑点还是白点构成树,使用矩阵树定理统计答案即可。
时间复杂度\(\mathcal O(nm\cdot \alpha(nm)+k^3)\)
Code
int n,m,MOD;
int f[MAXM],a[MAXN][MAXN],id[MAXN][MAXN],tag[MAXM];
char s[MAXN][MAXN];
int Find(int x){
if(x == f[x]) return x;
return f[x] = Find(f[x]);
}
void Union(int x,int y){
x = Find(x); y = Find(y);
if(x == y) return;
f[x] = y;
}
int power(int x,int y){
int res = 1;
while(y){
if(y & 1) res = 1ll * res * x % MOD;
x = 1ll * x * x % MOD;
y >>= 1;
}
return res;
}
void add_edge(int u,int v){
addmod(a[u][u],1); addmod(a[v][v],1);
submod(a[u][v],1); submod(a[v][u],1);
}
int Gauss(int n){
int res = 1;
for(int k = 1;k <= n;k++){
int pos = k;
for(int i = k + 1;i <= n;i++){
if(abs(a[pos][k]) < abs(a[i][k]))
pos = i;
}
if(pos != k) res = sub(0,res), swap(a[pos],a[k]);
res = 1ll * res * a[k][k] % MOD;
ll inv = power(a[k][k],MOD - 2);
for(int i = k;i <= n;i++)
a[k][i] = 1ll * a[k][i] * inv % MOD;
for(int i = k + 1;i <= n;i++){
for(int j = k + 1;j <= n;j++)
submod(a[i][j],1ll * a[i][k] * a[k][j] % MOD);
}
}
return res;
}
int Solve(int t){
int tot = 0;
for(int i = 0;i <= n;i++){
for(int j = 0;j <= m;j++){
if(((i + j) & 1) == t)
id[i][j] = ++tot;
}
}
for(int i = 1;i <= tot;i++) f[i] = i;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(s[i][j] == '/' && ((i + j - 1) & 1) == t)
Union(id[i - 1][j],id[i][j - 1]);
if(s[i][j] == '\\' && ((i + j) & 1) == t)
Union(id[i - 1][j - 1],id[i][j]);
}
}
int cnt = 0;
for(int i = 1;i <= tot;i++){
if(Find(i) == i)
tag[i] = ++cnt;
}
memset(a,0,sizeof(a));
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(s[i][j] == '*' && ((i + j - 1) & 1) == t)
add_edge(tag[Find(id[i - 1][j])],tag[Find(id[i][j - 1])]);
if(s[i][j] == '*' && ((i + j) & 1) == t)
add_edge(tag[Find(id[i - 1][j - 1])],tag[Find(id[i][j])]);
}
}
return Gauss(cnt - 1);
}
int main(){
scanf("%d%d%d",&n,&m,&MOD);
for(int i = 1;i <= n;i++) scanf("%s",s[i] + 1);
printf("%d\n",add(Solve(0),Solve(1)));
return 0;
}
AGC 020D
Solution
首先最小连续长度\(k=\left\lceil\dfrac{max(a,b)}{min(a,b)+1}\right\rceil\),证明的话考虑将出现次数小的字符塞到出现次数大的字符中间。
如果\(k=1\),那么直接\(ABAB\cdots\)排列即可,下面考虑\(k>1\)的情况。
为了使字典序最小,考虑按位添加字符,这一位能填\(A\)当且仅当
- 连续的\(A\)长度不能超过\(k\)
- 后面能继续填下去,即后面不能出现连续\(k+1\)个相同字符。
那么不难发现前缀一定是\(AA\cdots ABAA\cdots AB\cdots\),即\(A\)重复\(k\)遍后面接一个\(B\)。而后缀一定是\(\cdots ABB\cdots BABB\cdots B\),即\(B\)重复\(k\)遍后面接一个\(A\)。
接下来考虑如何找到前缀和后缀的分界点,然后就可以\(\mathcal O(1)\)计算每个位置的值了。不难发现最优情况下分界点处必定是一个\(A\),且这个\(A\)是前后缀共用的,假设分界点及其之前有\(N_B\)个\(B\)和\(N_A\)个\(A\),那么要满足\(N_B\leq max(0,\lfloor\frac{N_A-1}{k}\rfloor\)),同时也有\(N_A=A-\lceil\frac{B-N_B}{k}\rceil+1\),带入可得\(N_B\leq \left\lfloor\dfrac{A-\left\lceil\frac{B-N_B}{k}\right\rceil}{k}\right\rfloor\),由于要字典序最大,所以分界点要尽可能靠后,即\(N_B\)尽量大。发现\(N_B\)增大时,左边递增,右边递减,满足单调性,所以二分找到最大的\(N_B\)即可。
时间复杂度\(\mathcal O(Q\log n)\)
Code
const char s[2] = {'A','B'};
int A,B,C,D,Q,k;
int main(){
scanf("%d",&Q);
while(Q--){
scanf("%d%d%d%d",&A,&B,&C,&D);
k = ceil(1.0 * max(A,B) / (min(A,B) + 1));
if(k == 1){
for(int i = C - 1;i < D;i++)
putchar(s[(i + (B > A)) & 1]);
putchar('\n');
continue;
}
int l = 0,r = B,NB = 0;
while(l <= r){
int mid = (l + r) >> 1;
if((A - (B - mid + k - 1) / k) / k >= mid){
NB = mid;
l = mid + 1;
}else
r = mid - 1;
}
int pos = NB + A - (B - NB + k - 1) / k + 1;
for(int i = C - 1;i < D;i++)
putchar(i < pos ? s[i % (k + 1) == k] : s[(A + B - i) % (k + 1) != 0]);
putchar('\n');
}
return 0;
}
CF 627F
Solution
假设固定了矩形的上边界\(l\),考虑下边界\(r\)从\(n\)变到\(l\)时答案会发生什么变化
首先把在\([l,n]\)范围内有点的那几列拿出来,离散化之后,考虑枚举矩形的左边界\(i\),可选的左边界就是离散化后\(i\)和\(i−1\)之间的列数。对于可选的右边界,设\(nxt_i\)为最小的满足\([i,nxt_i]\)之间的点数大于等于\(k\)的数,那么右边界的方案数就是\(m+1-nxt_i\)。不难发现,在离散化之后,\(nxt_i\leq i+k−1\)一定成立。
接着考虑当下边界逐渐变化时,相当于删去某一行,那么对于若干列,它们的点数减一。我们可以用一个双向链表来维护此时有点的列,如果该列点数减一后消失就把它从链表中删去,而我们需要更改前面的数的\(nxt_i\),那么最多只需要更改前面的\(k−1\)个数和自己的\(nxt_i\)就够了,具体可以通过two-pointers实现。对于删掉的列,顺便改一下它后面那一列的可选左端点即可。
时间复杂度\(O(nmk)\)
Code
int n,m,k,p;
ll ans,res;
int nxt[MAXN],L[MAXN],R[MAXN],cnt[MAXN];
vector<int> vec[MAXN];
void Remove(int x){
int i = x;
while(L[i] && nxt[L[i]] >= x) i = L[i];
cnt[x] -= 1;
for(int j = L[i],num = 0;i <= x;i = R[i]){
while(j <= m && num < k) j = R[j], num += cnt[j];
res -= (i - L[i]) * (j - nxt[i]);
nxt[i] = j; num -= cnt[i];
}
if(!cnt[x]) L[R[x]] = L[x], R[L[x]] = R[x];
}
int main(){
scanf("%d%d%d%d",&n,&m,&p,&k);
for(int i = 1,x,y;i <= p;i++){
scanf("%d%d",&x,&y);
vec[x].push_back(y);
}
for(int i = 1;i <= n;i++){
memset(cnt,0,sizeof(cnt));
memset(L,0,sizeof(L));
memset(R,0,sizeof(R));
memset(nxt,0,sizeof(nxt));
for(int x = i;x <= n;x++){
for(int y : vec[x])
cnt[y] += 1;
}
int lst = 0;
for(int j = 1;j <= m;j++){
if(cnt[j]){
L[j] = lst;
R[lst] = j;
lst = j;
}
}
R[lst] = m + 1; L[m + 1] = lst;
res = 0;
for(int x = R[0],y = 0,num = 0;x <= m;x = R[x]){
while(y <= m && num < k) y = R[y], num += cnt[y];
res += 1ll * (x - L[x]) * (m + 1 - y);
nxt[x] = y; num -= cnt[x];
}
for(int j = n;j >= i;j--){
ans += res;
for(int x : vec[j]) Remove(x);
}
}
printf("%lld\n",ans);
return 0;
}
CF 582D
Solution
不难发现\(\dbinom{n}{k}\)中\(p\)的次数为\(\sum_i\left\lfloor\dfrac{n}{p^i}\right\rfloor −\left\lfloor\dfrac{k}{p^i}\right\rfloor −\left\lfloor\dfrac{n-k}{p^i}\right\rfloor\),而这个东西当且仅当\(p\)进制下\(n\)和\(n−k\)的第\(i−1\)位进位时为\(1\)否则为\(0\),所以\(p\)的次数就等价于\(k\)与\(n−k\)在\(p\)进制下相加时进位次数。
这个可以用数位dp解决,设\(f_{i,j,0/1,0/1}\)表示从大到小考虑到第\(i\)位,总共进位了\(j\)次,有没有卡到上界,以及第\(i−1\)位是否进位。转移并不需要枚举第\(i−1\)位是多少,只需要分类讨论一下\(n\)和\(k\)第\(i−1\)位的取值范围即可。
Code
int n,p,a,ans;
int d[MAXN],f[MAXN][MAXN][2][2],up[MAXN];
char s[MAXN];
int main(){
scanf("%d%d",&p,&a);
scanf("%s",s + 1);
int len = strlen(s + 1);
for(int i = 1;i <= len;i++) d[i] = s[len - i + 1] - '0';
while(len){
ll v = 0;
for(int i = len;i >= 1;i--){
v = v * 10 + d[i];
d[i] = v / p; v %= p;
if(!d[i] && i == len) len -= 1;
}
up[++n] = v;
}
f[n + 1][0][1][0] = 1;
for(int i = n;i >= 1;i--){
int a1 = 1ll * (p + 1) * p / 2 % MOD;
int a2 = 1ll * (up[i] + 1) * up[i] / 2 % MOD;
int a3 = 1ll * p * (p - 1) / 2 % MOD;
int a4 = 1ll * up[i] * (2 * p - up[i] - 1) / 2 % MOD;
int a5 = 1ll * up[i] * (up[i] - 1) / 2 % MOD;
int a6 = 1ll * up[i] * (2 * p - up[i] + 1) / 2 % MOD;
for(int j = 0;j + i <= n;j++){
int f1 = f[i + 1][j][0][0],f2 = f[i + 1][j][1][0],f3 = f[i + 1][j][0][1],f4 = f[i + 1][j][1][1];
f[i][j][0][0] = (1ll * f1 * a1 % MOD + 1ll * f2 * a2 % MOD + 1ll * f3 * a3 % MOD + 1ll * f4 * a4 % MOD) % MOD;
f[i][j][1][0] = (1ll * f2 * (up[i] + 1) % MOD + 1ll * f4 * (p - 1 - up[i]) % MOD) % MOD;
f[i][j + 1][0][1] = (1ll * f1 * a3 % MOD + 1ll * f2 * a5 % MOD + 1ll * f3 * a1 % MOD + 1ll * f4 * a6 % MOD) % MOD;
f[i][j + 1][1][1] = (1ll * f2 * up[i] % MOD + 1ll * f4 * (p - up[i]) % MOD) % MOD;
}
}
for(int i = a;i <= n;i++)
addmod(ans,add(f[1][i][0][0],f[1][i][1][0]));
printf("%d\n",ans);
return 0;
}
CF 698D
Solution
考虑依次判断每个怪是否能被射到,只要枚举射击的顺序,设为\(p_1,p_2,\cdots,p_k\),那么就是要用p_k去射怪,\(p_{k−1}\)去射\(p_k\)和怪之间的一个障碍 \(\cdots\) 以此类推。由于这个搜索最多只有\(\mathcal O(k)\)层,所以复杂度是\(\mathcal O(k)\)的,只需要预处理出每个人射每个怪时中间会被哪些怪挡住即可。那么总复杂度就是\(\mathcal O(n\times k!\times k)\)。
Code
int n,K,ans;
int p[MAXN],vis[MAXN];
pii a[MAXN],b[MAXN];
vector<int> vec[MAXK][MAXN],tmp;
pii operator - (const pii &x,const pii &y){
return make_pair(x.first - y.first,x.second - y.second);
}
ll operator ^ (const pii &x,const pii &y){
return 1ll * x.first * y.second - 1ll * x.second * y.first;
}
bool Check(int &dep,int u){
if(dep > K) return false;
for(int v : vec[p[dep]][u]){
if(vis[v]) continue;
dep += 1;
if(!Check(dep,v)) return false;
}
tmp.push_back(u); vis[u] = 1;
return true;
}
int main(){
scanf("%d%d",&K,&n);
for(int i = 1;i <= K;i++) scanf("%d%d",&a[i].first,&a[i].second);
for(int i = 1;i <= n;i++) scanf("%d%d",&b[i].first,&b[i].second);
for(int i = 1;i <= K;i++){
for(int j = 1;j <= n;j++){
for(int k = 1;k <= n;k++){
if(j == k) continue;
if(b[k].first >= min(a[i].first,b[j].first) && b[k].first <= max(a[i].first,b[j].first) &&
b[k].second >= min(a[i].second,b[j].second) && b[k].second <= max(a[i].second,b[j].second) &&
((a[i] - b[j]) ^ (a[i] - b[k])) == 0)
vec[i][j].push_back(k);
if((int)vec[i][j].size() == K)
break;
}
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= K;j++) p[j] = j;
do{
for(int v : tmp) vis[v] = 0;
tmp.clear();
int d = 1;
if(Check(d,i)) {ans += 1; break;}
}while(next_permutation(p + 1,p + 1 + K));
}
printf("%d\n",ans);
return 0;
}
AGC 026D
Solution
先来考虑是一个矩阵的情况,如果我们已经确定了上面一行,那么当前行有两种情况
-
每一列都与上一行对应列相反,显然可行。
-
如果上一行是\(ABAB\cdots\)或者\(BABA\cdots\)的情况,那么每一列都与上一行对应列相同,也可行。
于是我们可以在算出当前情况下最小的\(h_i\),然后以这样的\(h_i\)为分隔递归计算子矩形,算出\(f_{0/1}\),分别表示子矩形最上面一行是/不是形如\(ABAB\cdots\)的方案数,然后转移即可。
时间复杂度\(\mathcal O(n^2)\)
Code
int n;
int h[MAXN];
pii Solve(int l,int r,int bottom){
int x = INF;
for(int i = l;i <= r;i++) checkMin(x,h[i]);
int coef = 1,res = 1,cnt = 0;
for(int i = l;i <= r;i++){
if(h[i] == x) {cnt += 1; continue;}
int j = i;
while(j < r && h[j + 1] != x) j += 1;
pii val = Solve(i,j,x); i = j;
coef = 1ll * coef * val.first % MOD;
res = 1ll * res * add(val.first,val.second) % MOD;
}
return make_pair(1ll * coef * power(2,x - bottom) % MOD,add(1ll * res * power(2,cnt) % MOD,1ll * coef * sub(power(2,x - bottom),2) % MOD));
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&h[i]);
printf("%d\n",Solve(1,n,0).second);
return 0;
}
CF 526F
Solution
考虑将二维问题转化为一维问题,即构造一个序列\(a_{1\dots n}\),对于一个点\((x,y)\),\(a_x = y\)。那么原问题转化为经典的连续段计数问题。
因为本题没有重复元素,相当于统计满足\(max - min + 1 = len\)的区间个数。
考虑枚举右端点,用单调栈维护当前每个后缀的\(max\)和\(min\),然后用线段树维护当前每个后缀的\(max - min - len\)的值,以及每个后缀区间的值的最小值以及最小值的个数。用单调栈维护的具体过程可以参考CF 407E。
显然\(max-min\geq -1\),同时对于每个右端点\(r\),至少会有一个后缀的值为\(-1\)(当\(l = r\)时),因此每个右端点对答案的贡献都是当前后缀区间的值的最小值的个数。
时间复杂度\(\mathcal O(n \log n)\)。
Code
int n,top1,top2;
ll ans;
int a[MAXN],st1[MAXN],st2[MAXN];
namespace SegmentTree{
struct Tree{
int l,r;
int min,num,tag;
} tree[MAXN << 2];
#define lson k << 1
#define rson k << 1 | 1
void update(int k){
tree[k].min = min(tree[lson].min,tree[rson].min);
tree[k].num = (tree[k].min == tree[lson].min) * tree[lson].num + (tree[k].min == tree[rson].min) * tree[rson].num;
}
void apply(int k,int v){
tree[k].min += v;
tree[k].tag += v;
}
void down(int k){
if(tree[k].tag){
apply(lson,tree[k].tag);
apply(rson,tree[k].tag);
tree[k].tag = 0;
}
}
void Build(int k,int l,int r){
tree[k].l = l;
tree[k].r = r;
tree[k].num = r - l + 1;
if(l == r) return;
int mid = (l + r) >> 1;
Build(lson,l,mid);
Build(rson,mid + 1,r);
}
void Modify(int k,int l,int r,int v){
if(tree[k].l >= l && tree[k].r <= r){
apply(k,v);
return;
}
down(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if(l <= mid)
Modify(lson,l,r,v);
if(r > mid)
Modify(rson,l,r,v);
update(k);
}
int Query(){
return tree[1].num;
}
#undef lson
#undef rson
}
int main(){
scanf("%d",&n);
for(int i = 1,x,y;i <= n;i++){
scanf("%d%d",&x,&y);
a[x] = y;
}
SegmentTree::Build(1,1,n);
for(int i = 1;i <= n;i++){
while(top1 && a[st1[top1]] < a[i]){
SegmentTree::Modify(1,st1[top1 - 1] + 1,st1[top1],-a[st1[top1]]);
top1 -= 1;
}
st1[++top1] = i; SegmentTree::Modify(1,st1[top1 - 1] + 1,i,a[i]);
while(top2 && a[st2[top2]] > a[i]){
SegmentTree::Modify(1,st2[top2 - 1] + 1,st2[top2],a[st2[top2]]);
top2 -= 1;
}
st2[++top2] = i; SegmentTree::Modify(1,st2[top2 - 1] + 1,i,-a[i]);
SegmentTree::Modify(1,1,i,-1);
ans += SegmentTree::Query();
}
printf("%lld\n",ans);
return 0;
}
CF 526G
Solution
设叶子的个数为\(k\),若\(2y\geq k\),则\(y\)条路径可以覆盖整棵树,证明可以考虑先以任意方式匹配叶子,如果有两条路径不相交,可以调整成相交的情况。否则,选取的每条路径两端一定是叶子,一共要选\(2y\)个叶子,且一定存在一种方案满足链的并恰好是\(x\)到这\(2y\)个节点的链的并。
接着不难发现一个性质:一定存在一种方案使得直径的一端被选取,否则一定不优,考虑以直径的两个端点分别为根,每次询问在两颗树中分别查询即可。
由于直径的端点一定是叶子,所以问题就被转化为选\(2y-1\)个叶子,打通他们到根的链,并且最大化边权之和。不难发现这和长链剖分是等价的(因为长链的端点一定是叶子),即贪心的选取前\(2y-1\)个长链即可。
但是这\(2y-1\)个长链不一定经过\(x\),需要做一些调整,有两种调整方法。
- 将贡献最小的长链删去并选取\(x\)所在的长链。
- 找到离\(x\)最近的且被选取的长链将其下半部分替换为\(x\)所在长链。
只需要倍增地找到\(x\)的祖先中第一个在需要替换掉的链上的点即可。
时间复杂度\(\mathcal O((n+q)\log n)\)
Code
int n,Q,len,lastans;
int head[MAXN];
struct Edge{
int to,next,w;
} e[MAXN << 1];
void add_edge(int u,int v,int w){
e[++len] = (Edge){v,head[u],w};
head[u] = len;
}
struct Tree{
int rt,tot;
int dep[MAXN],son[MAXN],maxDep[MAXN],f[MAXN][LOG],top[MAXN],len[MAXN],belong[MAXN],sum[MAXN];
vector<int> vec;
void dfs1(int u,int fa){
if(dep[u] > dep[rt]) rt = u;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
dep[v] = dep[u] + e[i].w;
dfs1(v,u);
}
}
void dfs2(int u,int fa){
f[u][0] = fa;
for(int i = 1;i < LOG;i++) f[u][i] = f[f[u][i - 1]][i - 1];
maxDep[u] = dep[u];
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
dep[v] = dep[u] + e[i].w;
dfs2(v,u);
checkMax(maxDep[u],maxDep[v]);
if(maxDep[v] > maxDep[son[u]]) son[u] = v;
}
}
void dfs3(int u,int t){
top[u] = t;
if(son[u]) dfs3(son[u],t);
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v != f[u][0] && v != son[u])
dfs3(v,v);
}
}
void init(int x){
dfs1(x,0); memset(dep,0,sizeof(dep));
dfs2(rt,0); dfs3(rt,rt);
for(int i = 1;i <= n;i++){
if(top[i] == i){
vec.push_back(i);
len[i] = maxDep[i] - dep[f[i][0]];
}
}
tot = vec.size();
sort(vec.begin(),vec.end(),[&](const int &x,const int &y) {return len[x] > len[y];});
for(int i = 1;i <= tot;i++){
sum[i] = sum[i - 1] + len[vec[i - 1]];
for(int u = vec[i - 1];u;u = son[u]) belong[u] = i;
}
}
int jump(int x,int y){
for(int i = LOG - 1;i >= 0;i--){
if(belong[f[x][i]] > y)
x = f[x][i];
}
return f[x][0];
}
int query(int x,int y){
y = 2 * y - 1;
if(y >= tot) return sum[tot];
if(belong[x] <= y) return sum[y];
int u = jump(x,y); // replace
int v = jump(x,y - 1); // 2y - 1
return max(sum[y] + maxDep[x] - maxDep[u],sum[y - 1] + maxDep[x] - dep[v]);
}
} T1,T2;
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&Q);
for(int i = 1,u,v,w;i < n;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w); add_edge(v,u,w);
}
T1.init(1);
T2.init(T1.rt);
while(Q--){
int x,y;
scanf("%d%d",&x,&y);
x = (x + lastans - 1) % n + 1;
y = (y + lastans - 1) % n + 1;
printf("%d\n",lastans = max(T1.query(x,y),T2.query(x,y)));
}
return 0;
}
AGC 030F
Solution
先把所有\(A_{2i−1}\)和\(A_{2i}\)都有值的位置去掉,那么会剩下若干对形如\((−1,−1)\)和\((x,−1)\)(其中所有\((−1,−1)\)之间的相对顺序关系先不考虑,最后把答案乘上其个数的阶乘即可),其中\(x\)为钦定的数。
考虑从大往小填每一个数,这样就保证了在填完一对\((2i-1,2i)\)时,\(min(A_{2i-1},A_{2i})\)为当前填的这个数,也就是保证了无后效性。然后就可以dp了。
设\(f_{i,j,k}\)表示当前填完了\(\geq i\)的数,有\(j\)对\((-1,-1)\)已经填了一个数,有\(k\)对未匹配的\((x,-1)\)的方案数,接下来考虑如\(f_{i+1,j,k}\)何转移
-
\(i\)不是钦定的数,那么\(x\)可以不匹配,即转移到\(f_{i,j+1,k}\),也可以匹配掉\((-1,-1)\)中的某一对,转移到\(f_{i,j-1,k}\),还可以和一对\((x,-1)\)匹配,此时有\(k\)种匹配方法,乘\(k\)后转移到\(f_{i,j,k-1}\)。
-
\(i\)是钦定的数,那么\(x\)可以不匹配,即转移到\(f_{i,j,k+1}\),也可以匹配掉\((-1,-1)\)中的某一对,转移到\(f_{i,j-1,k}\)。
时间复杂度\(\mathcal O(n^3)\)
Code
int n,m,ans,tot;
int a[MAXM],b[MAXM],f[MAXM][MAXN][MAXN],vis[MAXM],flag[MAXM];
int main(){
scanf("%d",&n);
for(int i = 1,x,y;i <= n;i++){
x = (i << 1) - 1; y = (i << 1);
scanf("%d%d",&a[x],&a[y]);
if(a[x] > 0 && a[y] > 0){
vis[a[x]] = vis[a[y]] = 1;
continue;
}
if(a[x] == -1 && a[y] == -1) tot += 1;
else flag[(a[x] == -1) ? a[y] : a[x]] = 1;
}
for(int i = 1;i <= (n << 1);i++){
if(!vis[i])
b[++m] = i;
}
f[m + 1][0][0] = 1;
for(int i = m;i >= 1;i--){
for(int j = 0;j <= n;j++){
for(int k = 0;k <= n;k++){
if(!flag[b[i]]){
addmod(f[i][j + 1][k],f[i + 1][j][k]);
if(j) addmod(f[i][j - 1][k],f[i + 1][j][k]);
if(k) addmod(f[i][j][k - 1],1ll * f[i + 1][j][k] * k % MOD);
}else{
addmod(f[i][j][k + 1],f[i + 1][j][k]);
if(j) addmod(f[i][j - 1][k],f[i + 1][j][k]);
}
}
}
}
ans = f[1][0][0];
for(int i = 1;i <= tot;i++)
ans = 1ll * ans * i % MOD;
printf("%d\n",ans);
return 0;
}
CF 521E
Solution
不难发现,有解当且仅当存在两个环有边相交。
对于每个连通块先随便找一颗生成树,通过树上差分找出一条被至少两个环经过的边,然后从这条边中深度较深那个点去dfs它的子树,找到两条对应的非树边的起点,分别记为\(u_1\)和\(u_2\),它们对应的非树边的终点记为\(v_1\)和\(v_2\),且假设\(dep_{v_1}>dep_{v_2}\)
那么最终的答案的起点就是\(LCA(u_1,u_2)\),终点就是\(v_1\),然后直接把路径取出来即可。
时间复杂度\(\mathcal O(n)\)
Code
int n,m,len,u1,u2,v1,v2,p;
int head[MAXN],vis[MAXN],f[MAXN],dep[MAXN],d[MAXN];
vector<int> vec,tmp;
struct Edge{
int to,next;
} e[MAXN << 1];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
void dfs1(int u,int fa){
f[u] = fa; vis[u] = 1; dep[u] = dep[fa] + 1;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
if(!vis[v]) dfs1(v,u), d[u] += d[v];
else if(dep[v] < dep[u]) d[u] += 1, d[v] -= 1;
}
}
void dfs2(int u,int fa){
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(dep[v] == dep[u] + 1) dfs2(v,u);
else if(dep[v] < dep[p] && v != fa){
if(u1) u2 = u, v2 = v;
else u1 = u, v1 = v;
}
if(u2) return;
}
}
int LCA(int u,int v){
while(u != v){
if(dep[u] > dep[v]) u = f[u];
else v = f[v];
}
return u;
}
void GetPath(int u,int v){
int t = v;
if(dep[u] < dep[v]){
tmp.clear(); v = f[v];
while(u != v)
tmp.push_back(v), v = f[v];
tmp.push_back(u);
for(int i = tmp.size() - 1;i >= 0;i--)
vec.push_back(tmp[i]);
}else{
while(u != v)
vec.push_back(u), u = f[u];
}
vec.push_back(t);
}
void print(){
printf("%d",vec.size());
for(int x : vec) printf(" %d",x);
printf("\n"); vec.clear();
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i = 1,u,v;i <= m;i++){
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
for(int i = 1;i <= n;i++){
if(!vis[i])
dfs1(i,0);
}
for(int i = 1;i <= n;i++){
if(d[i] >= 2){
p = i;
break;
}
}
if(!p) {puts("NO"); return 0;}
puts("YES");
dfs2(p,f[p]);
if(dep[v2] < dep[v1]) swap(u1,u2), swap(v1,v2);
int lca = LCA(u1,u2);
GetPath(lca,u1);
GetPath(v1,v2);
print();
GetPath(lca,u2);
vec.push_back(v2);
print();
GetPath(lca,v2);
print();
return 0;
}
AGC 020E
Solution
考虑对于一个固定的\(0/1\)串如何求解方案数,可以通过区间dp,设\(f_{l,r}\)表示将区间\([l,r]\)编码的方案数,\(g_{l,r}\)表示将区间\([l,r]\)编码成单个字符或由一个括号括起来(允许嵌套)的方案数,转移时考虑第一位是否编码成一个字符
接下来考虑原问题,设\(f_S\)表示\(S\)及其所有子集的方案数,
在\(g\)的转移中枚举\(d\)后将划分出的子串并起来再进行转移,使用map来存储并记忆化搜索即可。
复杂度\(T(n)=\sum_{i=1}^n(n-i+1)(1+\sum_{d|i}(i+T(d))\),简单打个表可以发现当\(n=100\)时,\(T(n)=243422222\),能过。
Code
/*
Problem :
Algorithm :
Status :
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#define DEBUG cerr << "Passing Line " << __LINE__<< " in Function [" << __FUNCTION__ << "].\n";
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<class T> inline bool checkMax(T &a,const T &b) {return a < b ? a = b,1 : 0;}
template<typename T, typename...Args> inline void checkMax(T &a,const Args...arg) {checkMax(a,max(arg...));}
template<class T> inline bool checkMin(T &a,const T &b) {return a > b ? a = b,1 : 0;}
template<typename T, typename...Args> inline void checkMin(T &a,const Args...arg) {checkMin(a,min(arg...));}
const int INF = 0x3f3f3f3f;
const ll llINF = 1e18;
const int MOD = 998244353;
const int MAXN = 105;
void addmod(int &x,int y) {x += y; if(x >= MOD) x -= MOD;}
void submod(int &x,int y) {x -= y; if(x < 0) x += MOD;}
int add(int x,int y) {addmod(x,y); return x;}
int sub(int x,int y) {submod(x,y); return x;}
string s;
map<string,int> f,g;
int GetG(string s);
int GetF(string s){
if(s == "") return 1;
if(f.count(s)) return f[s];
int n = s.length(), res = 0;
for(int i = 1;i <= n;i++)
addmod(res,1ll * GetG(s.substr(0,i)) * GetF(s.substr(i,n - i + 1)) % MOD);
return f[s] = res;
}
int GetG(string s){
if(s == "") return 1;
if(s == "0") return 1;
if(s == "1") return 2;
if(g.count(s)) return g[s];
int n = s.length(), res = 0;
for(int d = 1;d < n;d++){
if(n % d != 0) continue;
string t = "";
for(int i = 0;i < d;i++){
bool x = 1;
for(int j = i;j < n;j += d) x &= s[j] - '0';
t += x + '0';
}
addmod(res,GetF(t));
}
return g[s] = res;
}
int main(){
cin >> s;
printf("%d\n",GetF(s));
return 0;
}
AGC 039E
Solution
下面令\(n=2N\),首先把环从\(n\)那里断开变成一条链,然后假设与\(1\)配对的点是\(i\)。
那么我们接下来就要对\([2,i)\cup (i,n]\)之间的点继续配对,由于要构成一颗树,这就相当于在\([2,i)\)中选一些点\(p_1<p_2<\cdots <p_x\),在\((i,n]\)中选一些点\(q_1>q_2>\cdots >q_x\),令\(p_i\)和\(q_i\)连线,对于剩下那些没被选到的点,在它们之间连线,求合法方案数。
考虑dp,设\(f_{l,i,r}\)表示考虑到\([l,r]\)这段区间中的点,\(i\)和向区间外部连线,其余点的连线要和\(i\)的连线连通的方案数。最后求的即为\(\sum f_{2,i,n}\)。
考虑如何转移,枚举\(p_1\)和\(q_1\),记为\(j\)和\(k\),然后先考虑\((j,i)\)之间的点,它们连出的边要么和\(j\)连出的这条边相连,要么和\(i\)连出的这条边相连,且一定存在一个分界点\(p\),满足\(p\)和之前的都与\(j\)连出的边相连,\(p+1\)和之后的都与\(i\)连出的边相连,那么可以递归为一个\([l,j)\cup (j,p]\)的子问题
同理定义(i,k)的分界点q,那么这也可以递归为一个\([q,k)∪(k,r]\)的子问题。而中间那部分就是一个\([p+1,i)∪(i,q−1]\)的子问题
所以易得转移方程\(f_{l,i,r}\leftarrow \sum f_{p+1,i,q-1}f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\)
时间复杂度\(\mathcal O(n^7)\),由于常数极小所以能通过。
下面考虑优化,先枚举\(p,q\),然后枚举\(i\),那么\(f_{l,i,r}+=f_{p+1,i,q-1}\sum f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\),不难发现后面和\(i\)无关,设后面的部分为\(s\),考虑如何快速计算\(s\)。
令\(g_{l,k,p}=\sum f_{l,j,p}[a_{j,k}=1]\),那么\(s=\sum g_{l,k,p}f_{q,k,r}\),转移\(f\)的时候顺便转移一下\(g\)即可,时间复杂度为\(\mathcal O(n^5)\)
Code
int n;
ll f[MAXN][MAXN][MAXN],g[MAXN][MAXN][MAXN];
char s[MAXN][MAXN];
int main(){
scanf("%d",&n); n <<= 1;
for(int i = 1;i <= n;i++) scanf("%s",s[i] + 1);
for(int i = 2;i <= n;i++){
f[i][i][i] = 1;
for(int j = i + 1;j <= n;j++) g[i][j][i] = (s[i][j] == '1');
}
for(int len = 3;len < n;len += 2){
for(int l = 2;l + len - 1 <= n;l++){
int r = l + len - 1;
for(int p = l;p <= r;p += 2){
for(int q = r;q > p;q -= 2){
ll sum = 0;
for(int k = q;k <= r;k++) sum += g[l][k][p] * f[q][k][r];
for(int i = p + 1;i <= q;i++) f[l][i][r] += sum * f[p + 1][i][q - 1];
}
}
for(int i = l;i <= r;i++){
for(int j = i + 1;j <= n;j++){
if(s[i][j] == '1')
g[l][j][r] += f[l][i][r];
}
}
}
}
ll ans = 0;
for(int i = 2;i <= n;i++){
if(s[1][i] == '1')
ans += f[2][i][n];
}
printf("%lld\n",ans);
return 0;
}
AGC 024D
Solution
首先发现一点,设直径的长度为\(D\),那么答案的下界是\(\lceil \frac{D}{2}\rceil\)(因为两棵树如果最大深度不同那么肯定不同构),而且这个下界很容易就可以达到。
如果\(D\)是奇数,那么这棵树的中心就是一条边,我们把这条边的两个端点作为根分别考虑两棵子树,那么要求深度相同的点的子节点个数相同,设\(c_i\)为所有深度为\(i\)的点的儿子数量的\(max\),那么最小的叶子个数即为\(\prod c_i\)
如果\(D\)是偶数,要么是以中心点为根,要么是中心点和与其相邻的点一起作为中心(满足两棵子树的最大深度都小于等于\(\frac{D}{2}\)),将两种情况的答案取\(max\)即可。
时间复杂度\(\mathcal O(n^2)\)
Code
int n,len,D,rt,d;
ll ans = llINF;
int head[MAXN],f[MAXN],dis[MAXN],maxSon[MAXN],dep[MAXN],ban[MAXN];
struct Edge{
int to,next;
} e[MAXN << 1];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
void dfs1(int u,int fa){
f[u] = fa; dis[u] = dis[fa] + 1;
if(dis[u] > dis[rt]) rt = u;
checkMax(D,dis[u]);
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
dfs1(v,u);
}
}
void dfs2(int u,int fa){
int cnt = 0; dep[u] = dep[fa] + 1;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(v == fa || ban[v]) continue;
cnt += 1;
dfs2(v,u);
}
checkMax(maxSon[dep[u]],cnt);
}
void Solve(int u,int v){
memset(maxSon,0,sizeof(maxSon));
maxSon[1] = 2; dep[0] = 1;
ban[u] = ban[v] = 1;
dfs2(v,0); dfs2(u,0);
ban[u] = ban[v] = 0;
ll c = 1;
for(int i = 1;i <= d;i++) c *= maxSon[i];
checkMin(ans,c);
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i = 1,u,v;i < n;i++){
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
dfs1(1,0); int t = rt; rt = 0; dfs1(t,0);
d = (D + 1) >> 1;
printf("%d ",d);
if(D & 1){
int u = rt;
for(int i = 1;i < d;i++) u = f[u];
dfs2(u,0); ll c = 1;
for(int i = 1;i < d;i++) c *= maxSon[i];
checkMin(ans,c);
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
Solve(u,v);
}
}else{
int u = rt;
for(int i = 1;i < d;i++) u = f[u];
int v = f[u];
Solve(u,v);
}
printf("%lld\n",ans);
return 0;
}
AGC 032D
Solution
显然操作等价于将某个数左移或者右移。
考虑dp,设\(f_{i,j}(0\leq j \leq n)\)表示前\(i\)个数排好序,最后一个不动的元素是\(j\),的最小代价,转移有两种情况
- \(p_i>j\),此时若\(i\)不动,则\(f_{i-1,j}\to f_{i,p_i}\),若\(i\)选择右移则\(f_{i-1,j}+A\to f_{i,j}\)
- \(p_i<j\),此时\(i\)只能左移,则\(f_{i-1,j}+B\to f_{i,j}\)
时间复杂度\(\mathcal O(n^2)\)
Code
int n,A,B;
int p[MAXN];
ll f[MAXN][MAXN];
int main(){
scanf("%d%d%d",&n,&A,&B);
for(int i = 1;i <= n;i++) scanf("%d",&p[i]);
memset(f,0x3f,sizeof(f));
f[0][0] = 0;
for(int i = 1;i <= n;i++){
for(int j = 0;j <= n;j++){
if(p[i] > j){
checkMin(f[i][j],f[i - 1][j] + A);
checkMin(f[i][p[i]],f[i - 1][j]);
}else
checkMin(f[i][j],f[i - 1][j] + B);
}
}
ll ans = llINF;
for(int i = 0;i <= n;i++) checkMin(ans,f[n][i]);
printf("%lld\n",ans);
return 0;
}
CF 538H
Solution
首先不考虑\(t,T\)的限制,记\(L\)为第一个小组的学生数量,\(R\)为第二个小组的学生数量,那么不难发现\(R= \min_{i=1}^n r_i\),\(L = \max_{i=1}^n l_i\)是最优的。
证明如下:
将所有\([l_i, r_i]\)当作一条数轴上的线段,那么有三种情况:
- 有三条线段两两不交,则无解。
- 所有线段两两有交,即有\(R\geq L\),则在这种情况下,每个老师都可以随意地选择小组。
- \(R < L\),在这种情况下,显然\(R\)不能更大,而\(R\)取更小的值则不优,\(L\)同理。
因此\(R = \min_{i=1}^n r_i\),\(L = \max_{i=1}^n l_i\)一定是最优的。
下面考虑\(t,T\)的限制,不难发现,若\(L+R<t\),则无论哪种情况下,只将\(L\)增大成\(t-R\)是最优的,若\(L+R>T\),则无论哪种情况下,只将\(R\)减小成\(T-L\)是最优的。
因此可以直接求出最优的\(L,R\),最后根据老师的限制做一次二分图染色判断是否可行即可。
时间复杂度\(\mathcal O(n+m)\)。
Code
int n,m,len,L,R = INF,T,t;
int head[MAXN],col[MAXN],l[MAXN],r[MAXN];
struct Edge{
int to,next;
} e[MAXN << 1];
void add_edge(int u,int v){
e[++len] = (Edge){v,head[u]};
head[u] = len;
}
int dfs(int u){
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(!col[v]){
col[v] = 3 - col[u];
if(!dfs(v)) return 0;
}else if(col[v] == col[u]) return 0;
}
return 1;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&t,&T,&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d%d",&l[i],&r[i]);
checkMax(L,l[i]); checkMin(R,r[i]);
}
if(L + R < t) L = t - R;
if(L + R > T) R = T - L;
if(L < 0 || R < 0) {puts("IMPOSSIBLE"); return 0;}
for(int i = 1;i <= n;i++){
bool a = (L >= l[i] && L <= r[i]), b = (R >= l[i] && R <= r[i]);
if(!a && !b) {puts("IMPOSSIBLE"); return 0;}
if(a && !b) col[i] = 1;
if(b && !a) col[i] = 2;
}
for(int i = 1,u,v;i <= m;i++){
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
for(int i = 1;i <= n;i++){
if(col[i] && !dfs(i)){
puts("IMPOSSIBLE");
return 0;
}
}
for(int i = 1;i <= n;i++){
if(!col[i]){
col[i] = 1;
if(!dfs(i)){
puts("IMPOSSIBLE");
return 0;
}
}
}
printf("POSSIBLE\n%d %d\n",L,R);
for(int i = 1;i <= n;i++) putchar('0' + col[i]);
return 0;
}
ARC 100F
Solution
首先答案就是\(a\)序列的总出现次数减去其在不 colorful 序列中的总出现次数,而前面那个的答案就是\((n−m+1)×k^{n−m}\),即枚举\(a\)出现的位置,考虑每个位置对答案的贡献。
考虑计算后面那个,分成三种情况考虑
-
\(a\)序列就是一个 colorful 序列,显然答案为\(0\)。
-
否则,若\(a_{1...m}\)中的数互不相同,但由于\(m<k\)所以不是 colorful 序列。我们设\(f_{i,j}\)表示考虑前\(i\)个位置,当前极长没有重复颜色的后缀长度为\(j\)的方案数,初值为\(f_{0,0}=1\),最终要求的即为\(\sum_{j=0}^{k-1}f_{n,j}\),转移如下
用后缀和优化就可以做到\(\mathcal O(nk)\)的复杂度。
同理我们可以设一个\(g_{i,j}\)表示\(f_{i,j}\)中\(a_{1...m}\)的个数,转移类似,当\(j\geq m\)时让\(g_{i,j}+=f_{i,j}\)即可,算出最终的\(g\)后除以\(k^{\underline{m}}\)即为答案。
- 否则,这意味着\(a_{1...m}\)中有相同的数且不是 colorful 序列。先求出极长的没有重复数的前后缀长度,设为\(l,r\),同样采用上面的dp,只是将初值改成\(f_{0,0...l}=g_{0,0..r}=1\),然后将\(f\)和\(g\)都第二种情况转移即可。(注意这里的\(g\)不是上面的\(g\),它和\(f\)的意义是一样的,也代表方案数),最终枚举\(a_{1...m}\)的开头位置,那么答案就是
时间复杂度\(\mathcal O(nk)\)
Code
int n,k,m,ans,tot;
int a[MAXN],f[MAXN][MAXK],g[MAXN][MAXK],vis[MAXK];
bool check(int l,int r){
memset(vis,0,sizeof(vis));
for(int i = l;i <= r;i++){
if(vis[a[i]]) return false;
vis[a[i]] = 1;
}
return true;
}
bool colorful(){
for(int i = 1;i + k - 1 <= m;i++)
if(check(i,i + k - 1)) return true;
return false;
}
void Solve(int (*f)[MAXK]){
for(int i = 1;i <= n;i++){
for(int j = 1;j < k;j++)
f[i][j] = add(1ll * sub(f[i - 1][j - 1],f[i - 1][j]) * (k - j + 1) % MOD,f[i - 1][j]);
for(int j = k - 1;j >= 0;j--)
addmod(f[i][j],f[i][j + 1]);
}
}
int main(){
scanf("%d%d%d",&n,&k,&m);
tot = 1ll * power(k,n - m) * (n - m + 1) % MOD;
for(int i = 1;i <= m;i++) scanf("%d",&a[i]);
if(colorful()) {printf("%d\n",tot); return 0;}
int l = m, r = 1;
memset(vis,0,sizeof(vis));
for(int i = 1;i <= m;i++){
if(vis[a[i]]) {l = i - 1; break;}
vis[a[i]] = 1;
}
memset(vis,0,sizeof(vis));
for(int i = m;i >= 1;i--){
if(vis[a[i]]) {r = m - i; break;}
vis[a[i]] = 1;
}
if(l == m){
f[0][0] = 1;
for(int i = 1;i <= n;i++){
for(int j = 1;j < k;j++){
f[i][j] = add(1ll * sub(f[i - 1][j - 1],f[i - 1][j]) * (k - j + 1) % MOD,f[i - 1][j]);
g[i][j] = add(1ll * sub(g[i - 1][j - 1],g[i - 1][j]) * (k - j + 1) % MOD,g[i - 1][j]);
if(j >= m) addmod(g[i][j],f[i][j]);
}
for(int j = k - 1;j >= 0;j--){
addmod(f[i][j],f[i][j + 1]);
addmod(g[i][j],g[i][j + 1]);
}
}
ans = g[n][0];
for(int i = k;i > k - m;i--) ans = 1ll * power(i,MOD - 2) * ans % MOD;
}else{
for(int i = 0;i <= l;i++) f[0][i] = 1;
for(int i = 0;i <= r;i++) g[0][i] = 1;
Solve(f); Solve(g);
for(int i = 0;i <= n - m;i++)
addmod(ans,1ll * f[i][0] * g[n - m - i][0] % MOD);
}
printf("%d\n",sub(tot,ans));
return 0;
}
CF 704C
Solution
将每个表达式看成一个点。定义一个点的值为这个点对应的表达式的值,一个连通块的值为连通块内点的值的异或,整个图的值为所有连通块的值的异或。我们要求的就是整个图的值为\(1\)的方案数。
由于保证\(x_i\)和\(x_{-i}\)在所有表达式中一共只会出现最多两次,如果两个表达式存在相同的\(x_{|i|}\),则连一条边,那么这张图一定是一堆单独的环和链,因为每个点的度数至多为\(2\)。
假设我们求出了每个连通块的值为\(0/1\)的方案数,那么 dp 一次即可求出整个图的值为\(0/1\)的方案数,设\(c_{i,0/1}\)表示第\(i\)个连通块值为\(0/1\)的方案数。设\(f_{i,0/1}\)表示只考虑前\(i\)个连通块,异或值为\(0/1\)的方案数,不难得出转移方程\(f_{i,j} = f_{i-1,j} \times c_{i,0} + f_{i-1,j \operatorname{xor} 1} \times c_{i,1}\)。
现在考虑如何对于每个连通块求\(c_{0/1}\)。
注意到连出来的图一定由若干个连通块组成的,而连通块只有四种形态,我们分别考虑。
-
一个点的链
表达式形如\(x_i\),则\(c_{0} = c_1 = 1\)。
表达式形如\(x_i \operatorname{or} x_j\),则\(c_0 = 1\),\(c_1 = 3\)。 -
一个点的环
表达式形如\(x_i \operatorname{or} x_i\),则\(c_0 = c_1 = 1\)。
表达式形如\(x_i \operatorname{or} x_{-i}\),则\(c_0 = 0\),\(c_1 = 2\)。 -
至少两个点的链
此时链的两端对应的表达式可能形如\(x_i\),也可能形如 \(x_i \operatorname{or} x_j\)。
如果形如\(x_i\),可以把它当做\(x_i \operatorname{or} 0\)看待,然后依然考虑 dp。
设第\(i\)个点上的表达式为\(x_{p_i} \operatorname{or} x_{q_i}\),满足\(x_{|q_{i-1}|} = x_{|p_i|}\)。
设\(g_{i,0/1,0/1}\)表示只考虑前\(i\)个点,值为\(0/1\),\(x_{|q_i|} = 0/1\)的方案数。
转移时对于\(x_{|p_i|},x_{|q_i|}\)的每种取值,\(g_{i,(x_{p_i} \operatorname{or} x_{q_i}) \operatorname{xor} j,x_{|q_i|}} \leftarrow g_{i-1,j,x_{|p_i|}}\)。 -
至少两个点的环
随便选一条边断开,转化成至少两个点的链的情况。
对于断开处的变量,枚举它的值\(w\)为\(0/1\),然后把两端当做\(x_i \operatorname{or} w\)看待。然后用至少两个点的链的方法做即可。
Code
咕咕咕
CF 568E
Solution
设原来给定的序列为\(a\),能填的序列为\(b\)。
考虑经典LIS的\(\mathcal O(n\log n)\)做法,我们维护了一个数组\(f[i]\)表示长度为\(i\)的上升子序列的最小结尾,每次可以二分。考虑魔改一下这个做法,对于\(a_i \neq-1\)的位置仍然二分,对于\(a_i=-1\)的位置枚举这个位置的值,则转化为不是\(-1\)的情况。事实上这里并不需要二分,先对\(b\)排序后用双指针维护即可。这样的复杂度为\(\mathcal O(n\log n+(n+m)k)\)。
下面考虑如何输出方案,一般的想法是对每个元素记录转移到它的位置,但是空间限制不容许我们对\(a_i=-1\)的位置这么做。考虑只记录\(a_i\neq -1\)的位置的前驱,倒序输出的时候我们只跳到\(\neq -1\)的位置,如果它的前驱仍然满足\(\neq -1\)就直接跳,否则判断前面是否存在一个\(\neq -1\)的位置可以转移到当前位置,若存在则直接跳过去,若不存在则跳到上一个\(-1\)的位置即可。
最后对于不在LIS中\(-1\),随便填一个没有用过的数即可。
时间复杂度\(\mathcal O(n\log n+(n+m)k)\)
Code
int n,m;
int a[MAXN],b[MAXN],len[MAXN];
vector<pii> f;
multiset<int> s;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
a[n + 1] = INF;
scanf("%d",&m);
for(int i = 1;i <= m;i++) scanf("%d",&b[i]), s.insert(b[i]);
sort(b + 1,b + 1 + m);
m = unique(b + 1,b + 1 + m) - b - 1;
f.emplace_back(0,-1);
for(int i = 1;i <= n + 1;i++){
if(a[i] != -1){
if(f.back().first < a[i]){
len[i] = (int)f.size();
f.emplace_back(a[i],i);
}else{
len[i] = lower_bound(f.begin(),f.end(),make_pair(a[i],-1)) - f.begin();
f[len[i]] = make_pair(a[i],i);
}
}else{
for(int j = m, k = f.size();j >= 1;j--){
while(k && f[k - 1].first >= b[j]) k -= 1;
if(k == (int)f.size()) f.emplace_back(b[j],-1);
else f[k] = make_pair(b[j],-1);
}
}
}
for(int i = n + 1;i >= 1;){
for(int j = i - 1,cnt = 0;j >= 0;j--){
if(a[j] == -1) {cnt += 1; continue;}
if(a[j] >= a[i]) continue;
int l = upper_bound(b + 1,b + 1 + m,a[j]) - b;
int r = lower_bound(b + 1,b + 1 + m,a[i]) - b;
if(min(r - l,cnt) == len[i] - len[j] - 1){
auto it = s.find(b[l]);
checkMin(cnt,r - l);
for(int k = j + 1;k < i && cnt;k++){
if(a[k] == -1){
a[k] = *it; s.erase(it);
it = s.upper_bound(a[k]);
cnt -= 1;
}
}
i = j; break;
}
}
}
for(int i = 1;i <= n;i++){
if(a[i] == -1){
a[i] = *s.begin();
s.erase(s.begin());
}
}
for(int i = 1;i <= n;i++) printf("%d ",a[i]);
return 0;
}
CF 582E
Solution
首先建出表达式树,由于ABCD总共只有\(2^4=16\)种不同的情况,考虑dp,设\(f_{u,s}\)表示考虑到\(u\)这个节点,\(s\)是一个\(16\)位的二进制数,其中\(s\)的第\(i\)位为\(1\)当且仅当ABCD的取值是第\(i\)种情况时这个子树的值为\(1\),否则为\(0\),不难发现转移为\(f_{u,x}|y=f_{ls,x}\times f_{rs,y}\),也就是说转移实际上是一个卷积,所以用FWT优化转移即可。
Code
int n,m,tot;
int val[MAXN][5],f[MAXS][MAXM],fa[MAXS];
char s[MAXS],c[MAXS];
vector<int> son[MAXS];
void FWTOR(int *a,int n,int flag){
for(int i = 2;i <= n;i <<= 1){
for(int j = 0;j < n;j += i){
for(int k = j;k < j + (i >> 1);k++){
if(~flag) addmod(a[k + (i >> 1)],a[k]);
else submod(a[k + (i >> 1)],a[k]);
}
}
}
}
void FWTAND(int *a,int n,int flag){
for(int i = 2;i <= n;i <<= 1){
for(int j = 0;j < n;j += i){
for(int k = j;k < j + (i >> 1);k++){
if(~flag) addmod(a[k],a[k + (i >> 1)]);
else submod(a[k],a[k + (i >> 1)]);
}
}
}
}
void MulOR(int *A,int *B,int *C){
static int a[MAXM],b[MAXM],c[MAXM];
for(int i = 0;i < m;i++) a[i] = A[i], b[i] = B[i];
FWTOR(a,m,1); FWTOR(b,m,1);
for(int i = 0;i < m;i++) c[i] = 1ll * a[i] * b[i] % MOD;
FWTOR(c,m,-1);
for(int i = 0;i < m;i++) addmod(C[i],c[i]);
}
void MulAND(int *A,int *B,int *C){
static int a[MAXM],b[MAXM],c[MAXM];
for(int i = 0;i < m;i++) a[i] = A[i], b[i] = B[i];
FWTAND(a,m,1); FWTAND(b,m,1);
for(int i = 0;i < m;i++) c[i] = 1ll * a[i] * b[i] % MOD;
FWTAND(c,m,-1);
for(int i = 0;i < m;i++) addmod(C[i],c[i]);
}
void dfs(int u){
if(son[u].empty()){
if(c[u] == '?'){
for(int i = 0;i < 4;i++){
int S = 0;
for(int j = 0;j < n;j++) S |= val[j][i] << j;
addmod(f[u][S],1); addmod(f[u][(m - 1) ^ S],1);
}
}else{
int x = c[u] - (c[u] >= 'A' && c[u] <= 'D' ? 'A' : 'a'), S = 0;
for(int j = 0;j < n;j++) S |= val[j][x] << j;
addmod(f[u][c[u] >= 'A' && c[u] <= 'D' ? S : ((m - 1) ^ S)],1);
}
return;
}
dfs(son[u][0]); dfs(son[u][1]);
if(c[u] != '&') MulOR(f[son[u][0]],f[son[u][1]],f[u]);
if(c[u] != '|') MulAND(f[son[u][0]],f[son[u][1]],f[u]);
}
int main(){
scanf("%s%d",s + 1,&n); m = (1 << n);
int len = strlen(s + 1); tot = 1;
for(int i = 1,p = 1;i <= len;i++){
if(s[i] == '(') son[p].push_back(++tot), fa[tot] = p, p = tot;
else if(s[i] == ')') p = fa[p];
else c[p] = s[i];
}
for(int i = 0;i < n;i++){
for(int j = 0;j < 5;j++) scanf("%d",&val[i][j]);
}
dfs(1); int S = 0;
for(int i = 0;i < n;i++) S |= val[i][4] << i;
printf("%d\n",f[1][S]);
return 0;
}

浙公网安备 33010602011771号