來自蒟蒻的 銳 鈍評:三道簽到題:A,C,F(評價為水中水);稍有思考:E(賽場上想到了不知為何寫WA了);沒想到:B,D,G。G題更是重量級,細節要比其他題多不少
部分idea來自我的隊友zhuge0和laonongmin,我一人是補不完的(悲)
A
題意:給出一串身份證碼,提取特定位數判斷生日是否為合理日期
直接模擬注意閏年判斷即可
B
題意:給出長度為n的陣列,詢問所有區間裡\(\max(a_l,a_{l+1},...,a_r)\times\min(a_l,a_{l+1},...,a_r)\times(r-l+1)\)的最大值
首先確定列舉物件,由於當某個區間的最小值確定後,只需讓其左右區間儘可能的向大擴大即可,故可以列舉以a[i]為最小值的最大區間左右端點和區間內最大值
附上ac程式碼,採用了構建笛卡爾樹求區間大小,線段樹查詢區間最值,用__int128來完成高精運算。
其實完全沒必要,直接用單調棧求以上所有值,再用python寫高精,大概十行寫完。因為想練一下資料結構 博主太懶 後面再補吧~~
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e6+10;
int a[N],l[N],r[N],f[N],l_sz[N],r_sz[N];
int n;
#define int __int128
inline void read(int &n){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
n=x*f;
}
inline void print(int n){
if(n<0){
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
//cout<<'\n';
}
#undef int
struct node
{
int maxn,minn;
int l,r;
}tr[N<<2];
void pushup(int p)
{
tr[p].maxn=max(tr[p<<1].maxn,tr[p<<1|1].maxn);
tr[p].minn=min(tr[p<<1].minn,tr[p<<1|1].minn);
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r)
{
tr[p].maxn=tr[p].minn=a[l];
return ;
}
int m=(l+r)>>1;
build(l,m,p<<1);
build(m+1,r,p<<1|1);
pushup(p);
}
int query_1(int l,int r,int p,int x,int y)
{
if(l>y||r<x) return 0;
if(x<=l&&y>=r) return tr[p].maxn;
int m=(l+r)>>1;
return max(query_1(l,m,p<<1,x,y),query_1(m+1,r,p<<1|1,x,y));
}
int dfs(int x)
{
if(~f[x]) return f[x];
l_sz[x]=0;r_sz[x]=0;
if(l[x]) {l_sz[x]++;l_sz[x]+=dfs(l[x]);}
if(r[x]) {r_sz[x]++;r_sz[x]+=dfs(r[x]);}
return f[x] = l_sz[x]+r_sz[x];
}
void build()
{
int root = 0;
stack<int> stk;
for(int i=1;i<=n;++i)
{
int last = 0;
while(stk.size()&&a[i]<a[stk.top()])
{
last = stk.top();
stk.pop();
}
if(!stk.empty()) r[stk.top()] = i;
else root = i;
l[i] = last;
stk.push(i);
}
//for(int i=1;i<=n;++i) cout<<l[i]<<' '<<r[i]<<'\n';
}
void solve()
{
memset(f,-1,sizeof f);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
build(); //構建笛卡爾樹
build(1,n,1); //構建線段樹
__int128 ans = 0;
for(int i=1;i<=n;++i)
{
dfs(i); //查詢i點子樹的大小
__int128 L = i-l_sz[i], R = i+r_sz[i];
__int128 minn = a[i],maxn = query_1(1,n,1,L,R);
ans = max(ans,minn*maxn*(R-L+1));
}
print(ans);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T=1;
//cin>>T;
while(T--)
{
solve();
}
}
C
題意:n+m個均勻分佈的隨機變數,最大值出現在前n個的機率
嗯~~,直接輸出n\(\div (n+m)\) 即可
D
題意:一個長度為n的01串,規定當全為1或再連續的長為x的0串後接上長度至少為x的1串,每一位出現0的機率為p,1的機率為1-p,求該字串為合法字串的機率
首先根據機率DP的基本思想,\(f[k] = p\times f[k-1] + \sum_{i=1}^{\lfloor {k\div 2}\rfloor} p^i\times (1-p)^i\times f[k-2i]\)
狀態轉移:若第k位為1且前k-1位為一個合法串,機率為\(p\times f[k-1]\),否則若前k-1位不是合法串,且能透過構造變成合法串共有\(\lfloor {k\div 2}\rfloor\)種情況,直接將機率加和即可
在此發現較難維護的是後者,可以用\(g[k] = \sum_{i=1}^{\lfloor {k\div 2}\rfloor} p^i\times (1-p)^i\times f[k-2i]\) 維護g陣列
g陣列的轉移推導:\(p\times (1-p)\times g[k-2] = \sum_{i=2}^{\lfloor {k\div 2}\rfloor} p^i\times (1-p)^i\times f[k-2i]\)
可以發現二者差值為 \(p\times(1-p)\times f[k-2]\)
則有\(g[k] = p\times (1-p)\times (g[k-2] + f[k-2])\)
附上ac程式碼
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int mod = 998244353;
//求快速冪和逆元
ll quick_pow(ll a,int b)
{
a%=mod;
ll ans=1;
for(;b;b>>=1)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
ll inv(ll x)
{
return quick_pow(x,mod-2);
}
void solve()
{
int n,p;
cin>>n>>p;
ll P = p*inv(100)%mod,NP = (100-p)*inv(100)%mod;
vector<ll> f(n+1),g(n+1);
f[0] = 1, f[1] = P, f[2] = (P*f[1] + NP*P)%mod;
g[1] = 0, g[2] = P*NP%mod;
for(int i=3;i<=n;++i)
{
//注意此處P*NP後需要先取mod繼續乘
g[i] = P*NP%mod*(g[i-2]+f[i-2])%mod;
f[i] = (P*f[i-1] + g[i])%mod;
}
cout << f[n] << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T=1;
//cin>>T;
while(T--)
{
solve();
}
}
E
題意:n棵樹環形排列,每天每顆樹生長高度為上一棵樹的高度,即\(a[i] = a[i] + a[i-1]\),有q次查詢求每次查詢區間裡有多少棵樹大於查詢值hi(hi<=1e18)
顯然整體上指數倍增,只需模擬不超過63次,然後每次都輸出區間長度即可,但直接用py模擬63次會超時,可能是博主太菜 在此加上一個標記看有多少數大於1e18,當標記值==n後直接輸出區間長度,可以加速模擬(似乎不能穩定過TLE,不過懶得改了)
附上ac程式碼
n, q = map(int, input().split())
INF = 1e18
a = [0] * n
a[0:n] = list(map(int, input().split()))
num = 0
for i in range(0, q):
l, r, h = map(int, input().split())
l -= 1
r -= 1
if num >= n:
print(r - l + 1)
else:
cnt = 0
tmp = a[n - 1]
num = 0
for j in range(n - 1, 0, -1):
if a[j] <= INF:
a[j] += a[j - 1]
if j >= l and j <= r and a[j] > h:
cnt += 1
if a[j] > INF:
num += 1
a[0] += tmp
if 0 >= l and a[0] > h:
cnt += 1
if a[0] > INF:
num += 1
print(cnt)
F
題意:給你一個 \(10^{1000}\) 以內的數,問是否是 3 的倍數。
顯然讀入一下輸出答案即可
G(個人認為本場最難寫的題)
題意:有一個 n × m 的網格,可能為陸地或水域。每個網格上有若干遊客。只能四聯通走陸地,水域不能行走。有 k 個旅遊景點,影響力在初始座標中為 pi,每走一步,影響力減一,最多減到 0。對於一個格子,若有唯一一個影響力最大的景點,那麼這個格子的遊客都將前往這個景點。問最終每個景點的遊客數量。n, m ≤ 500,pi ≤ 106。
- 第一想法從k個景區出發bfs顯然是不行的,然後想最佳化
其實是看題解本題類似flood fill(只是借用下名字),將每個景點視作一個源點,其流量會隨著距離減小至0,那麼可以採取優先佇列的bfs,讓每個點被更新時就得到其最大流量值,以次來減少點的更新次數* - 具體實現上用val[i][j]表示其目前最大流量值,ne[i][j]表示其源頭,vis[i][j]表示是否為合法點(即至多有一個流量最大的源頭)*
- 在優先佇列裡若pi(即當前節點的流量)-1 > val[i][j](即下一步走向的節點)就更新val[i][j],並把(i,j)放入佇列,否則若pi==val[i][j]且id(即當前節點源頭的標號)與ne[i][j]不同,說明有至少兩個源頭該點是非法點,不再加入佇列*
- 但上面的思路還有問題,即使id == ne[i][j]也可能是非法點,因為當前節點就可能是非法點,所以即使當前節點和(i,j)有著相同的源頭,(i,j)也可能為非法點。加上一個判斷就能過了*
附上ac程式碼
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 510;
int t[N][N],vis[N][N];
int val[N][N],ne[N][N];
char g[N][N];
int ans[N*N];
int dir[4][2]={1,0,-1,0,0,1,0,-1};
struct node
{
int x,y,pi,id;
//x,y記錄當前點的座標,pi表示吸引值,id表示從哪個點轉移的
bool operator < (const node& T) const
{
return pi < T.pi;
}
};
void solve()
{
int n,m;
cin>>n>>m;
ll sum = 0;
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
cin>>g[i][j];
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
{
cin>>t[i][j];
sum+=t[i][j];
}
int k;
cin>>k;
priority_queue<node> q;
/*vector<node> pot(k);*/
for(int i=0;i<k;++i)
{
int x,y,pi;
cin>>pi>>x>>y;
x--;y--;
if(pi==0) continue;
q.push({x,y,pi,i});
val[x][y] = pi,ne[x][y] = i,vis[x][y] = 1;
}
//特判只有一個景區的情況
if(k==1) {cout<<sum<<'\n';return ;}
while(q.size())
{
auto [x,y,pi,id]=q.top();q.pop();
for(int i=0;i<4;++i)
{
int xx = x+dir[i][0];
int yy = y+dir[i][1];
//pi-1不能為0
if(xx>=0&&xx<n&&yy>=0&&yy<m&&pi-1>=1&&g[xx][yy]=='*')
{
if(pi-1>val[xx][yy])
{
vis[xx][yy] = vis[x][y];
val[xx][yy] = pi-1;
ne[xx][yy] = id;
q.push({xx,yy,pi-1,id});
}
else if(pi-1==val[xx][yy])
{
if (id != ne[xx][yy])
vis[xx][yy] = -1;
else if (vis[x][y] == -1)
vis[xx][yy] = -1;
}
}
}
}
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
{
if(vis[i][j]==1)
{
ans[ne[i][j]]+=t[i][j];
}
}
for(int i=0;i<k;++i) cout<<ans[i]<<" \n"[i==k-1];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T=1;
//cin>>T;
while(T--)
{
solve();
}
}