NOIP2024(欢乐)加赛3

NOIP2024(欢乐)加赛3

\(T1\) CF2033B Sakurako and Water \(100pts\)

  • 枚举主对角线取 \(\max\) 即可。

    点击查看代码
    ll a[510][510];
    int main()
    {
    	ll t,n,ans=0,maxx,i,j,k,h;
    	cin>>t;
    	for(h=1;h<=t;h++)
    	{
    		cin>>n;
    		ans=0;
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				cin>>a[i][j];
    			}
    		}
    		for(k=-n+1;k<=n+1;k++)
    		{
    			maxx=0;
    			for(i=1,j=i+k;i<=n;i++,j++)
    			{
    				if(i<=n&&1<=j&&j<=n&&a[i][j]<0)
    				{
    					maxx=max(maxx,-a[i][j]);
    				}
    			}
    			ans+=maxx;
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

\(T2\) CF2025B Binomial Coefficients, Kind Of \(100pts\)

  • 观察样例加打表可知, \(2^{[k \ne n] \times k}\) 即为所求。

    • 已知递推式 \(f_{i,j}=\begin{cases} 1 & j=0 \lor j=n \\ f_{n,k-1}+f_{n-1,k-1} & j \in [1,n) \end{cases}\) ,省去 \(j=n\) 的状态后有 \(f_{i,j}=f_{i+1,j}\)
    • \(f_{i,j}\) 能更新到的值只有 \(\begin{cases} f_{i,j+1}=f_{i+1,j+1}=2f_{i,j} \end{cases}\) ,故 \(2^{[k \ne n] \times k}\) 即为所求。
    点击查看代码
    const ll p=1000000007;
    ll n[100010],m[100010],C[1010][1010];
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll t,i;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n[i];
    	}
    	for(i=1;i<=t;i++)
    	{
    		cin>>m[i];
    		if(m[i]==n[i])
    		{
    			cout<<1<<endl;
    		}
    		else
    		{
    			cout<<qpow(2,m[i],p)<<endl;
    		}
    	}
    	return 0;
    }
    

\(T3\) CF2030D QED's Favorite Permutation \(100pts\)

  • 对于移动,我们可以无脑进行交换来保证移动,然后将中途交换的位置再交换回去。

  • 通过手摸不难发现, \(p_{i}\) 能移动到 \(i\) 当且仅当 \(s_{\min(i,p_{i}) \sim \max(i,p_{i})}\) 中不含有 LR 子串。

  • 反向考虑,即 LR 子串不被上述区间包含,差分判断即可。

    点击查看代码
    ll a[200010],d[200010];
    char s[200010];
    int main()
    {
    	ll t,n,m,x,sum,i,j; 
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n>>m;
    		sum=0;
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    			d[min(a[i],i)]++;
    			d[max(a[i],i)]--;
    		}
    		for(i=1;i<=n;i++)
    		{
    			d[i]+=d[i-1];
    		}
    		cin>>(s+1);
    		for(i=1;i<=n-1;i++)
    		{
    			if(s[i]=='L'&&s[i+1]=='R')
    			{
    				sum+=(d[i]!=0);
    			}
    		}
    		for(i=1;i<=m;i++)
    		{
    			cin>>x;
    			if(s[x]=='L')
    			{
    				s[x]='R';
    				if(x+1<=n&&s[x+1]=='R')
    				{
    					sum-=(d[x]!=0);
    				}
    				if(x-1>=1&&s[x-1]=='L')
    				{
    					sum+=(d[x-1]!=0);
    				}
    			}
    			else
    			{
    				s[x]='L';
    				if(x+1<=n&&s[x+1]=='R')
    				{
    					sum+=(d[x]!=0);
    				}
    				if(x-1>=1&&s[x-1]=='L')
    				{
    					sum-=(d[x-1]!=0);
    				}
    			}
    			if(sum!=0)
    			{
    				cout<<"No"<<endl;
    			}
    			else
    			{
    				cout<<"Yes"<<endl;
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			d[i]=0;
    		}
    	}
    	return 0;
    }
    

\(T4\) CF2025E Card Game \(10pts\)

  • 部分分

    • \(10pts\) :输出样例。
  • 正解

    • 对于除了花色为 \(1\) 的卡牌中, Alice 选择的卡牌数量必须 \(\le\) Bob 选择的卡牌数量。即若设 \(s_{i,0/1}\) 表示花色为 \(i\) 的卡牌 Alice / Bob选择的卡牌数量,则有 \(s_{1,0}-s_{1,1}=\sum\limits_{i=2}^{n}s_{i,1}-s_{i,0}(s_{i,1}-s_{i,0} \ge 0)\)
    • \(f_{i,j}\) 表示同种花色,前 \(i\) 大等级的卡牌中 Alice 多拿了 \(j\) 张牌且双方都没有多余的牌的方案数,状态转移方程为 \(f_{i,j}=[j-1 \ge 0] \times f_{i-1,j-1}+[j+1 \le m] \times f_{i-1,j+1}\) 。边界为 \(f_{0,0}=1\)
    • \(g_{i,j}\) 表示 \([2,i]\) 花色中 Bob 一共多拿了 \(j\) 张牌且双方都没有多余牌的方案数,状态转移方程为 \(g_{i,j}=\sum\limits_{k=0}^{j}g_{i-1,k} \times f_{m,j-k}\) ,边界为 \(g_{1,0}=1\)
    • 最终,有 \(\sum\limits_{i=0}^{m}g_{n,i} \times f_{m,i}\) 即为所求,可以使用矩阵快速幂加速。
    点击查看代码
    const ll p=998244353;
    ll f[2][510],g[2][510];
    int main()
    {
    // #define Issac
    #ifdef Issac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll n,m,ans=0,i,j,k;
    	cin>>n>>m;
    	f[0][0]=1;
    	for(i=1;i<=m;i++)
    	{
    		for(j=0;j<=i;j++)
    		{
    			f[i&1][j]=f[(i-1)&1][j+1];
    			if(j-1>=0)
    			{
    				f[i&1][j]=(f[i&1][j]+f[(i-1)&1][j-1])%p;
    			}
    		}
    	}
    	g[1][0]=1;
    	for(i=2;i<=n;i++)
    	{
    		for(j=0;j<=m;j++)
    		{
    			g[i&1][j]=0;
    			for(k=0;k<=j;k++)
    			{
    				g[i&1][j]=(g[i&1][j]+g[(i-1)&1][k]*f[m&1][j-k]%p)%p;
    			}
    		}
    	}
    	for(i=0;i<=m;i++)
    	{
    		ans=(ans+f[m&1][i]*g[n&1][i]%p)%p;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(T5\) CF1967D Long Way to be Non-decreasing \(16pts\)

  • 部分分

    • \(16pts\)
      • 考虑从 \(i\)\(b_{i}\) 连一条有向边,那么 \(i\) 能到达的点就是经过若干次操作后可以成为的值。
      • 答案上界显然为 \(m\) ,考虑二分答案。
      • \(check\) 时求出令 \(a_{i}\) 走至多 \(mid\) 步能到达的范围内不小于 \(a_{i-1}'\) 的数 \(a_{i}\) ,主席树空间开不下遂写了暴力。
    点击查看代码
    int a[1000010],b[1000010],vis[1000010],tot=0;
    vector<int>e[1000010],s[1000010];
    void dfs(int x,int rt)
    {
    	s[rt].push_back(x);
    	vis[x]=tot;
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(vis[e[x][i]]!=tot)
    		{
    			dfs(e[x][i],rt);
    		}
    	}
    }
    bool check(int mid,int n,int m)
    {
    	int last=0,minn;
    	for(int i=1;i<=n;i++)
    	{
    		minn=0x7f7f7f7f;
    		for(int j=0;j<s[a[i]].size()&&j<=mid;j++)
    		{
    			if(s[a[i]][j]>=last)
    			{
    				minn=min(minn,s[a[i]][j]);
    			}
    		}
    		last=minn;
    		if(last==0x7f7f7f7f)
    		{
    			return false;
    		}
    	}
    	return true;
    }
    int main()
    {
    	int testcase,n,m,l,r,mid,ans,i,j;
    	scanf("%d",&testcase);
    	for(j=1;j<=testcase;j++)
    	{
    		scanf("%d%d",&n,&m);
    		for(i=1;i<=m;i++)
    		{
    			e[i].clear();
    			s[i].clear();
    			vis[i]=0;
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    		}
    		for(i=1;i<=m;i++)
    		{
    			cin>>b[i];
    			e[i].push_back(b[i]);
    		}
    		for(i=1;i<=m;i++)
    		{
    			tot++;
    			dfs(i,i);
    		}
    		l=0;
    		r=m;
    		ans=-1;
    		while(l<=r)
    		{
    			mid=(l+r)/2;
    			if(check(mid,n,m)==true)
    			{
    				ans=mid;
    				r=mid-1;
    			}
    			else
    			{
    				l=mid+1;
    			}
    		}
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    
  • 正解

    • 考虑双指针枚举最终的 \(a_{i}'\) ,判断 \(a_{i}\) 走至多 \(mid\) 步能否到达。
    • 建反图方便断边后跑 \(DFS\) 。特殊分讨下有向基环树上两点间的距离即可。
    点击查看代码
    int a[1000010],b[1000010],dep[1000010],col[1000010],pos[1000010],rt[1000010],dfn[1000010],out[1000010],low[1000010],ins[1000010],cnt,tim,scc_cnt,tot,len;
    vector<int>e[1000010],scc[1000010];
    stack<int>s;
    void tarjan(int x)
    {
    	tim++;
    	dfn[x]=low[x]=tim;
    	ins[x]=1;
    	s.push(x);
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(dfn[e[x][i]]==0)
    		{
    			tarjan(e[x][i]);
    			low[x]=min(low[x],low[e[x][i]]);
    		}
    		else
    		{
    			if(ins[e[x][i]]==1)
    			{
    				low[x]=min(low[x],dfn[e[x][i]]);
    			}
    		}
    	}
    	if(dfn[x]==low[x])
    	{
    		scc_cnt++;
    		int k=0;
    		while(x!=k)
    		{
    			k=s.top();
    			ins[k]=0;
    			col[k]=scc_cnt;
    			scc[scc_cnt].push_back(k);
    			s.pop();
    		}
    	}
    }
    void dfs(int x,int fa,int root)
    {
    	tot++;
    	dfn[x]=tot;
    	rt[x]=root;
    	dep[x]=dep[fa]+1;
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(col[e[x][i]]==0&&e[x][i]!=fa)
    		{
    			dfs(e[x][i],x,root);
    		}
    	}
    	out[x]=tot;
    }
    int get_dis(int x,int y)
    {
    	if(x==y)
    	{
    		return 0;
    	}
    	if(col[x]==0&&col[y]==0)
    	{
    		return (rt[x]==rt[y]&&dfn[y]<=dfn[x]&&dfn[x]<=out[y])?dep[x]-dep[y]:0x7f7f7f7f;
    	}
    	if(col[x]!=0&&col[y]!=0)
    	{
    		if(col[x]==col[y])
    		{
    			return (pos[x]>pos[y])?pos[x]-pos[y]:pos[x]+(int)scc[col[x]].size()-pos[y];
    		}
    		else
    		{
    			return 0x7f7f7f7f;
    		}
    	}
    	if(col[x]==0)
    	{
    		int tmp=dep[x];
    		x=rt[x];
    		if(col[x]==col[y])
    		{
    			if(pos[x]==pos[y])
    			{
    				return tmp;
    			}
    			return (pos[x]>pos[y])?tmp+pos[x]-pos[y]:tmp+pos[x]+(int)scc[col[x]].size()-pos[y];
    		}
    		else
    		{
    			return 0x7f7f7f7f;
    		}
    	}
    	else
    	{
    		return 0x7f7f7f7f;
    	}
    }
    bool check(int mid,int n,int m)
    {
    	for(int i=1,j=1;i<=n;i++)
    	{
    		while(j<=m&&get_dis(a[i],j)>mid)
    		{
    			j++;
    		}
    		if(j>m)
    		{
    			return false;
    		}
    	}
    	return true;
    }
    void bfs(int x,int co)
    {
    	len++;
    	pos[x]=len;
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(col[e[x][i]]==co&&pos[e[x][i]]==0)
    		{
    			bfs(e[x][i],co);
    		}
    	}
    }
    int main()
    {
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int t,n,m,l,r,mid,ans,i,j,k;
    	scanf("%d",&t);
    	dep[0]=-1;
    	for(k=1;k<=t;k++)
    	{
    		scanf("%d%d",&n,&m);
    		for(i=1;i<=scc_cnt;i++)
    		{
    			scc[i].clear();
    		}
    		cnt=tim=scc_cnt=tot=0;
    		while(s.empty()==0)
    		{
    			s.pop();
    		}
    		for(i=1;i<=m;i++)
    		{
    			e[i].clear();
    			col[i]=dep[i]=rt[i]=dfn[i]=low[i]=ins[i]=pos[i]=0;
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    		}
    		for(i=1;i<=m;i++)
    		{
    			cin>>b[i];
    			e[b[i]].push_back(i);
    		}
    		for(i=1;i<=m;i++)
    		{
    			if(dfn[i]==0)
    			{
    				tarjan(i);
    			}
    		}
    		for(i=1;i<=m;i++)
    		{
    			if(scc[col[i]].size()==1&&b[i]!=i)
    			{
    				col[i]=0;
    			}
    		}
    		for(i=1;i<=scc_cnt;i++)
    		{
    			if(scc[i].size()!=1)
    			{
    				for(j=0;j<scc[i].size();j++)
    				{
    					dfs(scc[i][j],0,scc[i][j]);
    				}
    				len=0;
    				bfs(scc[i][0],i);
    			}
    		}
    		for(i=1;i<=m;i++)
    		{
    			if(b[i]==i&&scc[col[i]].size()==1)
    			{
    				dfs(i,0,i);
    				len=0;
    				bfs(i,col[i]);
    			}
    		}
    		l=0;
    		r=m;
    		ans=-1;
    		while(l<=r)
    		{
    			mid=(l+r)/2;
    			if(check(mid,n,m)==true)
    			{
    				ans=mid;
    				r=mid-1;
    			}
    			else
    			{
    				l=mid+1;
    			}
    		}
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    

\(T6\) CF2023D Many Games \(9pts\)

  • 部分分

    • \(7pts\) :爆搜。
    • \(9pts\) :背包。
    点击查看代码
    const double eps=1e-8;
    ll p[200010],w[200010];
    double ans=0;
    unordered_map<ll,double>f;
    unordered_map<ll,double>::iterator itt;
    set<ll,greater<ll> >s;
    set<ll,greater<ll> >::iterator it;
    vector<ll>tmp;
    int main()
    {
    	ll n,i;
    	cin>>n;
    	s.insert(0);
    	f[0]=1;
    	for(i=1;i<=n;i++)
    	{
    		cin>>p[i]>>w[i];
    		for(it=s.begin();it!=s.end();it++)
    		{
    			if(f.find(w[i]+*it)==f.end())
    			{
    				f[w[i]+*it]=f[*it]*p[i]/100.0;
    			}
    			else
    			{
    				f[w[i]+*it]=max(f[w[i]+*it],f[*it]*p[i]/100.0);
    			}
    			tmp.push_back(w[i]+*it);
    		}
    		while(tmp.empty()==0)
    		{
    			s.insert(tmp.back());
    			tmp.pop_back();
    		}
    	}
    	for(itt=f.begin();itt!=f.end();itt++)
    	{
    		if((double)itt->first*itt->second-ans>eps)
    		{
    			ans=(double)itt->first*itt->second;
    		}
    	}
    	printf("%.8lf\n",ans);
    	return 0;
    }
    
  • 正解

    • 首先先特判掉 \(p_{i}=0/100\) 的情况。
    • \(x=\prod\limits_{i \in S} \frac{p_{i}}{100},y=\sum\limits_{i \in S}w_{i}\) 。对于 \((p,w)\) 能加入集合 \(S\) 当且仅当 \(xy<x \times \frac{p}{100} \times(y+w)\) ,即 \(y<\frac{pw}{100-p}\) ,得到 \(\sum\limits_{i \in S}w_{i}\) 的上界为 \(2 \times 10^{5}\)
    • 对于同一个 \(p\) 一定优先选择 \(w\) 比较大的物品,不妨先对其降序排序。类似的,设同一个 \(p\) 已经选择了前 \(k\) 大的物品,能继续选择第 \(k+1\) 大的物品当且仅当 \((\frac{p}{100})^{k} \times \sum\limits_{i=1}^{k}w_{i}<(\frac{p}{100})^{k+1} \times \sum\limits_{i=1}^{k+1}w_{i}\) ,又因为 \(kw_{k+1} \le \sum\limits_{i=1}^{k}w_{i}\)\(w_{i+1} \le \frac{\sum\limits_{i=1}^{k}w_{i}}{k}\),联立得到 \((\frac{p}{100})^{k} \times \sum\limits_{i=1}^{k}w_{i} \le (\frac{p}{100})^{k+1} \times \frac{k+1}{k} \times \sum\limits_{i=1}^{k}w_{i}\) ,即 \(k \le \frac{p}{100-p}\) ,于是只有前 \(1+\frac{p}{100-p} \le 100\) 个数才可能成为最优解。
    点击查看代码
    double f[200010];
    vector<ll>v[110];
    vector<pair<ll,ll> >e;
    int main()
    {
    // #define Issac
    #ifdef Issac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll n,p,w,sum=0,i,j,k;
    	double ans=0;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>p>>w;
    		if(1<=p&&p<=99)
    		{
    			v[p].push_back(w);
    		}
    		sum+=(p==100)*w;
    	}
    	for(i=1;i<=99;i++)
    	{
    		sort(v[i].begin(),v[i].end(),greater<ll>());
    		k=i/(100-i);
    		for(j=0;j<v[i].size()&&j<=k;j++)
    		{
    			e.push_back(make_pair(i,v[i][j]));
    		}
    	}
    	f[0]=1;
    	for(i=0;i<e.size();i++)
    	{
    		for(j=200000;j>=e[i].second;j--)
    		{
    			f[j]=max(f[j],f[j-e[i].second]*e[i].first/100.0);
    		}
    	}
    	for(i=0;i<=200000;i++)
    	{
    		ans=max(ans,1.0*(sum+i)*f[i]);
    	}
    	printf("%.8lf\n",ans);
    	return 0;
    }
    

总结

  • \(T4\) 的部分分没来得及写。
  • \(T5\)
    • 以为最终连成的一个环,破环为链后分前后缀考虑,然后就口胡了个假的势能线段树维护在线二维数点。基本全场都在写 \(T5\)
    • 需要特殊处理的部分想清楚再调,找基环树和求基环树上两点距离调了一个下午、一个晚上没调出来,但第二天想清楚后半小时就调完了。

后记

  • 貌似 \(RMJ\) \(miaomiao\) 是拿他的公号交的,可能是学校 \(OJ\) 远端评测比赛的初试(?)。
posted @ 2024-11-10 21:04  hzoi_Shadow  阅读(33)  评论(2编辑  收藏  举报
扩大
缩小