最近省队前联考被杭二成七南外什么的吊锤得布星,拿一场Div. 2恢复信心
然后Div.2 Rk3、Div. 1+Div. 2 Rk9,rating大涨200引起舒适
现在的Div. 2都怎么了,最难题难度都快接近3K了……
记\(a_i\)的前缀最大值为\(Max_i\),那么要求的就是\(Max_i = i\)的\(i\)的数量
#include#include #include #include #include #include #include #include #include #include
注意到如果字符串最右侧是一个'<'或者最左侧是一个'>'的话,我们只需要一直对最右边或者最左边的字符进行操作就可以满足条件
所以我们只需要在字符串首尾删去尽可能少的字符使得其最右侧是一个‘<’或者最左侧是一个'>'。
#include#include #include #include #include #include #include #include #include #include
从大到小枚举选择的乐曲中最小的愉悦值,用一个堆维护愉悦值大于等于当前枚举值的所有乐曲的播放时间前\(k\)大
#include#include #include #include #include #include #include #include #include #include
可以证明最优的划分方案中,\(1\)与所有点都有一条边。所以直接计算\(\sum\limits_{i=2}^{n-1} i \times (i+1)\)即可
我觉得可以直接从样例看出这个结论
#include#include #include #include #include #include #include #include #include #include
既然不能存在长度\(>1\)的奇回文串,那么一定不能存在长度为\(3\)的回文串,也就意味着\(\forall i \in [1,n-2] , a_i \neq a_{i+2}\)
把下标为奇数和下标为偶数的\(a_i\)拿出来作为两个序列\(b_i,c_i\),那么我们只需要\(b_i \neq b_{i+1} , c_i \neq c_{i+1}\)就是合法的方案
对于两个序列分别dp,设\(f_{i,0/1}\)表示当前已经填完前\(i\)个数,且第\(i\)个数的值与\(i\)之后的第一个有限制的位置的值是否相等的方案数,转移分有限制的点和无限制的点。
边界情况有点多需要注意。
#include#include #include #include //This code is written by Itstusing namespace std;#define int long longinline int read(){ int a = 0; char c = getchar(); bool f = 0; while(!isdigit(c) && c != EOF){ if(c == '-') f = 1; c = getchar(); } if(c == EOF) exit(0); while(isdigit(c)){ a = a * 10 + c - 48; c = getchar(); } return f ? -a : a;}const int MAXN = 2e5 + 7 , MOD = 998244353;int dp[MAXN][2] , arr[MAXN] , N , K;inline int poww(int a , int b){ int times = 1; while(b){ if(b & 1) times = times * a % MOD; a = a * a % MOD; b >>= 1; } return times;}signed main(){#ifndef ONLINE_JUDGE freopen("in","r",stdin); //freopen("out","w",stdout);#endif N = read(); K = read(); for(int i = 1 ; i <= N ; ++i) arr[i] = read(); vector < int > ind; ind.clear(); for(int i = 1 ; i <= N ; i += 2) if(arr[i] != -1) ind.push_back((i + 1) >> 1); int times1 , times2; if(!ind.empty()){ dp[0][0] = 1; int pos = 0; for(int i = 1 ; pos < ind.size() ; ++i) if(ind[pos] == i){ dp[i][0] = dp[i - 1][0]; if(++pos < ind.size() && arr[ind[pos] * 2 - 1] == arr[ind[pos - 1] * 2 - 1]) swap(dp[i][0] , dp[i][1]); } else{ if(i == 1){ dp[i][0] = K - 1; dp[i][1] = 1; } else{ dp[i][0] = (dp[i - 1][0] * (K - 2) + dp[i - 1][1] * (K - 1)) % MOD; dp[i][1] = dp[i - 1][0]; } } --pos; times1 = poww(K - 1 , (N + 1) / 2 - ind[pos]) * (dp[ind[pos]][0] + dp[ind[pos]][1]) % MOD; } else times1 = K * poww(K - 1 , (N + 1) / 2 - 1) % MOD; ind.clear(); for(int i = 2 ; i <= N ; i += 2) if(arr[i] != -1) ind.push_back(i >> 1); if(!ind.empty()){ memset(dp , 0 , sizeof(dp)); dp[0][0] = 1; int pos = 0; for(int i = 1 ; pos < ind.size() ; ++i) if(ind[pos] == i){ dp[i][0] = dp[i - 1][0]; if(++pos < ind.size() && arr[ind[pos] * 2] == arr[ind[pos - 1] * 2]) swap(dp[i][0] , dp[i][1]); } else{ if(i == 1){ dp[i][0] = K - 1; dp[i][1] = 1; } else{ dp[i][0] = (dp[i - 1][0] * (K - 2) + dp[i - 1][1] * (K - 1)) % MOD; dp[i][1] = dp[i - 1][0]; } } --pos; times2 = poww(K - 1 , N / 2 - ind[pos]) * (dp[ind[pos]][0] + dp[ind[pos]][1]) % MOD; } else times2 = K * poww(K - 1 , N / 2 - 1) % MOD; cout << times1 * times2 % MOD; return 0;}
如果你做过,这道题应该不难想到怎么做
对于题目中"\((x_1 , y_1) \in R , (x_1 , y_2) \in R , (x_2 , y_1) \in R , (x_2 , y_2) \neq R\)时将\((x_2,y_2)\)加入点集中"的操作,可以使用并查集描述:
并查集中存\(6 \times 10^5\)个点分别表示行和列,对于一个点\((x,y)\),将第\(x\)行和第\(y\)列在并查集中合并。注意到加入\((x_1 , y_1)(x_1 , y_2)(x_2 , y_1)\)之后,\(x_2\)行和\(y_2\)列就在同一个并查集里了,这就表示\((x_2,y_2)\)成为了点集\(R\)中的一个点。不难得到加完所有点后,答案是所有并查集包含的行数和列数的乘积之和。
注意到要删点,所以使用线段树分治+带撤销并查集做一下就可以了。
#include#include #include #include #include
考虑转移问题的优先级:我们要求\((u,v)(2 | u , 2|v)\)和\((u,v)(2 \not\mid u , 2 \not\mid v)\)的总经过次数最小,其次总边权最小。
既然两棵树同构,可以把这两棵树拍扁到一起变成一棵树,那么我们需要在这一棵树上经过边数最小,那么经过的显然是两点之间的路径。
上面的问题可以在拍扁的树上树形DP+倍增处理:设\(f_{i,j,k=0/1,l=0/1}\)表示:设从\(i\)开始跳\(2^j\)次方步到达的点为\(x\),那么原图中\((2 \times i + k , 2 \times x + l)\)的最短路是多少。注意上面的\(i,x\)指的是在拍扁的树上的编号。转移类似于矩阵乘法。
注意到转移优先级之后有可能答案不优,因为可能存在某些情况会绕一段路到另一棵树上,边权总和比直接走的边权要小。如果能将所有\((2i - 1 , 2i)\)的边权变为\(2i-1\)到\(2i\)之间的最短路长度,那么上面的情况就会覆盖\((2i - 1 , 2i)\)的边权,在DP过程中就不需要考虑了。
所以最后的问题是如何求出所有\(2i-1\)到\(2i\)的最短路。有一个很精妙的SSSP做法:建\(N+1\)个点编号为\(0\)到\(N\),连边\((0,i,w_{2i-1,2i})\),对于树上存在的边\((i,j,w_1,w_2)\)在图上连\((i,j,w_1+w_2)\),跑Dijkstra,得到\(0\)到\(i\)的最短路长度就是\(2i-1\)到\(2i\)的最短路长度。
#include#include #include #include //This code is written by Itstusing namespace std;#define int long longinline int read(){ int a = 0; char c = getchar(); while(!isdigit(c)) c = getchar(); while(isdigit(c)){ a = a * 10 + c - 48; c = getchar(); } return a;}const int MAXN = 3e5 + 7;int N , Q;struct Edge{int end , upEd , w;};inline void addEd(Edge *Ed , int *head , int &cntEd , int a , int b , int c , bool f = 0){ Ed[++cntEd] = (Edge){b , head[a] , c}; head[a] = cntEd; if(f){ Ed[++cntEd] = (Edge){a , head[b] , c}; head[b] = cntEd; }}namespace SSSP{#define PII pair < int , int >#define st first#define nd second Edge Ed[MAXN << 2]; int head[MAXN] , dis[MAXN]; int cntEd; priority_queue < PII > q; void work(){ memset(dis , 0x7f , sizeof(dis)); dis[0] = 0; q.push(PII(0 , 0)); while(!q.empty()){ PII t = q.top(); q.pop(); if(dis[t.nd] != -t.st) continue; for(int i = head[t.nd] ; i ; i = Ed[i].upEd) if(dis[t.nd] + Ed[i].w < dis[Ed[i].end]) q.push(PII(-(dis[Ed[i].end] = dis[t.nd] + Ed[i].w) , Ed[i].end)); } }}namespace Tree{ Edge Ed[MAXN << 1]; struct matrix{ int a[2][2]; matrix(){memset(a , 0x3f , sizeof(a));} int* operator [](int x){return a[x];} matrix operator *(matrix b){ matrix c; for(int i = 0 ; i < 2 ; ++i) for(int j = 0 ; j < 2 ; ++j) for(int k = 0 ; k < 2 ; ++k) c[j][k] = min(c[j][k] , a[j][i] + b[i][k]); return c; } }dis[MAXN][20] , tmp , initial , toX , toY; int head[MAXN] , dep[MAXN] , fa[MAXN][20] , val2[MAXN << 1] , cntEd; void dfs(int x , int p , matrix val){ dis[x][0] = val; fa[x][0] = p; for(int i = 1 ; fa[x][i - 1] ; ++i){ fa[x][i] = fa[fa[x][i - 1]][i - 1]; dis[x][i] = dis[x][i - 1] * dis[fa[x][i - 1]][i - 1]; } dep[x] = dep[p] + 1; for(int i = head[x] ; i ; i = Ed[i].upEd) if(Ed[i].end != p){ tmp[1][0] = min(Ed[i].w + SSSP::dis[Ed[i].end] , val2[i] + SSSP::dis[x]); tmp[0][1] = min(val2[i] + SSSP::dis[Ed[i].end] , Ed[i].w + SSSP::dis[x]); tmp[0][0] = min(Ed[i].w , tmp[0][1] + SSSP::dis[x]); tmp[1][1] = min(val2[i] , tmp[1][0] + SSSP::dis[x]); dfs(Ed[i].end , x , tmp); } } void init(){ memset(initial.a , 0 , sizeof(initial)); dfs(1 , 0 , initial); } int jump(int x , int y , bool f1 , bool f2){ if(dep[x] < dep[y]){ x ^= y ^= x ^= y; swap(f1 , f2); } toX = initial; toY = initial; toX[0][1] = toX[1][0] = SSSP::dis[x]; toY[0][1] = toY[1][0] = SSSP::dis[y]; for(int i = 18 ; i >= 0 ; --i) if(dep[x] - (1 << i) >= dep[y]){ toX = toX * dis[x][i]; x = fa[x][i]; } if(x == y) return toX[f1][f2]; for(int i = 18 ; i >= 0 ; --i) if(fa[x][i] != fa[y][i]){ toX = toX * dis[x][i]; toY = toY * dis[y][i]; x = fa[x][i]; y = fa[y][i]; } toX = toX * dis[x][0]; toY = toY * dis[y][0]; return min(toX[f1][0] + toY[f2][0] , toX[f1][1] + toY[f2][1]); } void work(int x , int y){ bool f1 = !(x & 1) , f2 = !(y & 1); x = (x + 1) >> 1; y = (y + 1) >> 1; printf("%lld\n" , jump(x , y , f1 , f2)); }}signed main(){#ifndef ONLINE_JUDGE freopen("in","r",stdin); freopen("out","w",stdout);#endif N = read(); for(int i = 1 ; i <= N ; ++i) addEd(SSSP::Ed , SSSP::head , SSSP::cntEd , 0 , i , read()); for(int i = 1 ; i < N ; ++i){ int a = read() , b = read() , w1 = read() , w2 = read(); addEd(SSSP::Ed , SSSP::head , SSSP::cntEd , a , b , w1 + w2 , 1); addEd(Tree::Ed , Tree::head , Tree::cntEd , a , b , w1 , 1); Tree::val2[Tree::cntEd] = Tree::val2[Tree::cntEd - 1] = w2; } SSSP::work(); Tree::init(); for(int Q = read() ; Q ; --Q) Tree::work(read() , read()); return 0;}