来自蒟蒻的 钝评:三道签到题: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();
	}
}
 posted on 2024-04-05 00:47  ruoye123456  阅读(70)  评论(0)    收藏  举报