bupt 2024 新生賽題解

ruoye123456發表於2024-04-05

來自蒟蒻的 鈍評:三道簽到題: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();
	}
}

相關文章