最近省隊前聯考被杭二成七南外什么的吊錘得布星,拿一場Div. 2恢復信心
然后Div.2 Rk3、Div. 1+Div. 2 Rk9,rating大漲200引起舒適
現在的Div. 2都怎么了,最難題難度都快接近3K了……
A. Detective Book
記\(a_i\)的前綴最大值為\(Max_i\),那么要求的就是\(Max_i = i\)的\(i\)的數量
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std;
inline 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;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N = read() , maxN = 0 , cnt = 0;
for(int i = 1 ; i <= N ; ++i){
maxN = max(maxN , read());
if(maxN == i) ++cnt;
}
cout << cnt;
return 0;
}
B. Good String
注意到如果字符串最右側是一個'<'或者最左側是一個'>'的話,我們只需要一直對最右邊或者最左邊的字符進行操作就可以滿足條件
所以我們只需要在字符串首尾刪去盡可能少的字符使得其最右側是一個‘<’或者最左側是一個'>'。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std;
inline 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;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int T;
for(cin >> T ; T ; --T){
string s;
int len;
cin >> len >> s;
if(s[0] == '<' && s[s.size() - 1] == '>'){
int cnt1 = 0 , cnt2 = 0;
for(int i = 0 ; i < s.size() && s[i] == '<' ; ++i)
++cnt1;
for(int i = s.size() - 1 ; i >= 0 && s[i] == '>' ; --i)
++cnt2;
cout << min(cnt1 , cnt2) << endl;
}
else cout << 0 << endl;
}
return 0;
}
C. Playlist
從大到小枚舉選擇的樂曲中最小的愉悅值,用一個堆維護愉悅值大於等於當前枚舉值的所有樂曲的播放時間前\(k\)大
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std;
#define int long long
inline 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;
}
#define PII pair < int , int >
#define st first
#define nd second
bool p(PII a , PII b){return a.st > b.st;}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N , K;
cin >> N >> K;
vector < PII > song;
priority_queue < int , vector < int > , greater < int > > q;
int sum = 0 , ans = 0;
for(int i = 1 ; i <= N ; ++i){
int k = read() , b = read();
song.push_back(PII(b , k));
}
sort(song.begin() , song.end() , p);
for(auto t : song){
q.push(t.nd);
sum += t.nd;
if(q.size() > K){
sum -= q.top();
q.pop();
}
ans = max(ans , t.st * sum);
}
cout << ans;
return 0;
}
D. Minimum Triangulation
可以證明最優的划分方案中,\(1\)與所有點都有一條邊。所以直接計算\(\sum\limits_{i=2}^{n-1} i \times (i+1)\)即可
我覺得可以直接從樣例看出這個結論
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std;
#define int long long
inline 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;
}
#define PII pair < int , int >
#define st first
#define nd second
bool p(PII a , PII b){return a.st > b.st;}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N , sum = 0;
cin >> N;
for(int i = 3 ; i <= N ; ++i)
sum = sum + i * (i - 1);
cout << sum;
return 0;
}
E. Palindrome-less Arrays
既然不能存在長度\(>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<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
//This code is written by Itst
using namespace std;
#define int long long
inline 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;
}
F. Extending Set of Points
如果你做過eJOI2018 元素周期表,這道題應該不難想到怎么做
對於題目中"\((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<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<stack>
#include<vector>
//This code is written by Itst
using namespace std;
#define int long long
inline 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 = 6e5 + 7;
#define PII pair < int , int >
#define st first
#define nd second
map < PII , int > mp;
vector < PII > Edge[MAXN << 2];
int fa[MAXN] , szX[MAXN] , szY[MAXN] , Q , ans;
int find(int x){
return fa[x] == x ? x : find(fa[x]);
}
#define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1)
void addEdge(int x , int l , int r , int L , int R , PII pos){
if(l >= L && r <= R){
Edge[x].push_back(pos);
return;
}
if(mid >= L) addEdge(lch , l , mid , L , R , pos);
if(mid < R) addEdge(rch , mid + 1 , r , L , R , pos);
}
void merge(int x , int y , stack < int > &stk){
x = find(x); y = find(y);
if(x == y) return;
ans -= szX[x] * szY[x] + szX[y] * szY[y];
if(szX[x] + szY[x] < szX[y] + szY[y])
swap(x , y);
fa[y] = x; szY[x] += szY[y]; szX[x] += szX[y];
ans += szX[x] * szY[x];
stk.push(y);
}
void work(int x , int l , int r){
stack < int > stk;
int lastans = ans;
for(auto t : Edge[x])
merge(t.st , t.nd + 300000 , stk);
if(l == r)
printf("%lld " , ans);
else{
work(lch , l , mid);
work(rch , mid + 1 , r);
}
while(!stk.empty()){
int t = stk.top(); stk.pop();
int p = find(t);
szX[p] -= szX[t]; szY[p] -= szY[t];
fa[t] = t;
}
ans = lastans;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
Q = read();
for(int i = 1 ; i <= Q ; ++i){
int a = read() , b = read();
PII t = PII(a , b);
if(mp.find(t) == mp.end()) mp[t] = i;
else{
addEdge(1 , 1 , Q , mp[t] , i - 1 , t);
mp.erase(mp.find(t));
}
}
for(auto t : mp)
addEdge(1 , 1 , Q , t.second , Q , t.first);
for(int i = 1 ; i <= 3e5 ; ++i)
szX[fa[i] = i] = 1;
for(int i = 3e5 + 1 ; i <= 6e5 ; ++i)
szY[fa[i] = i] = 1;
work(1 , 1 , Q);
return 0;
}
G. Double Tree
考慮轉移問題的優先級:我們要求\((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<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
//This code is written by Itst
using namespace std;
#define int long long
inline 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;
}