欢欢乐乐赛赛

欢欢乐乐赛赛

中文队名:回来吧,我的波波!

英文队名:Come back,my bobo!

队长: @Pursuing_OIer

队员: @hzoi_Shadow , @Charlie_ljk , @ccxswl

荣获铜牌🥉。

\(A\) P184. 树构造 \(AC\)

  • 强化版: luogu P10678 『STA - R6』月

  • 直接考虑直径最小的情况怎么做。

  • 设最终得到的直径为 \(l\) ,整棵树的高度为 \(d\) ,容易有 \(l \in [2d-1,2d]\) 。而 \(l\) 取到 \(2d-1\) 当且仅当根节点仅有一棵子树的高度为 \(d-1\) ,其他子树的高度都 \(<d-1\)

  • 最小化直径就可以转化为最小化高度,进一步转化为最大化每一层的节点数量(最后一层例外)。

  • 考虑将节点按照度数降序排序,从第二个点开始向序列中最靠前的且还有空余度数的节点连条边。

  • 如果到最后还有节点的度不为一则无解。

    点击查看代码
    struct node
    {
    	int du,id;
    }a[200010];
    pair<int,int>e[200010];
    bool cmp(node a,node b)
    {
    	return a.du>b.du;
    }
    int main()
    {
    	int n,m=0,flag=0,i,j;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i].du;
    		a[i].id=i;
    	}
    	sort(a+1,a+1+n,cmp);
    	for(i=1,j=2;i<=n&&j<=n;)
    	{
    		if(a[i].du>0)
    		{
    			m++;
    			e[m]=make_pair(a[i].id,a[j].id);//连到 a[i].id 上
    			a[i].du--;
    			a[j].du--;
    			j++;
    		}
    		else
    		{
    			i++;
    		}
    	}
    	if(m==n-1)
    	{
    		for(i=1;i<=n;i++)
    		{
    			if(a[i].du!=0)
    			{
    				cout<<"-1"<<endl;
    				flag=1;
    				break;
    			}
    		}
    		if(flag==0)
    		{
    			for(i=1;i<=m;i++)
    			{
    				cout<<e[i].first<<" "<<e[i].second<<endl;
    			}    
    		}
    	}
    	else 
    	{
    		cout<<"-1"<<endl;
    	}
    	return 0;
    }
    

\(B\) P186. 长途巴士

\(C\) T178. 你是黄金奖杯

\(D\) T3175. 地主斗

  • 赛时觉得是概率期望 \(DP\) ,赛后听 @Delov 说是大模拟。

\(E\) P195. Grouping

  • 原题: [ARC067E] Grouping

  • \(f_{i,j}\) 表示分到了人数为 \(i\) 的组使用了 \(j\) 个人的方案数,状态转移方程为 \(f_{i,j}=f_{i-1,j}+\sum\limits_{k=c}^{\min(d,\left\lfloor \dfrac{j}{i} \right\rfloor)}f_{i-1,j-ik} \dbinom{n-(j-ik)}{ik}g_{i,k}\) ,其中 \(g_{i,k}\) 表示将 \(ik\) 个人平均分成 \(k\) 组,不区分组内和组外顺序的方案数,有 \(g_{i,k}=\frac{1}{(ik!)}\prod\limits_{j=1}^{k}\dbinom{ij}{i}\) ,边界为 \(f_{a-1,0}=1\)

    • \(g_{i,k}\) 的组合意义指先从 \(ik\) 中选 \(i\) 个人,再从 \(i(k-1)\) 中选 \(i\) 个人,……,最后除以 \(A_{k}^{k}=(k)!\) 去重。
  • 最终有 \(f_{b,n}\) 即为所求。

    点击查看代码
    const ll p=1000000007;
    ll f[1010][1010],g[1010][1010],inv[1010],jc[1010],jc_inv[1010];
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    int main()
    {
    	ll n,a,b,c,d,i,j,k;
    	cin>>n>>a>>b>>c>>d;
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=n;i++)
    	{
    		inv[i]=(p-p/i)*inv[p%i]%p;
    		jc[i]=jc[i-1]*i%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(k=1;k<=n/i;k++)
    		{
    			g[i][k]=jc_inv[k];
    			for(j=1;j<=k;j++)
    			{
    				g[i][k]=g[i][k]*C(i*j,i,p)%p;
    			}
    		}
    	}
    	f[a-1][0]=1;
    	for(i=a;i<=b;i++)
    	{
    		for(j=0;j<=n;j++)
    		{
    			f[i][j]=f[i-1][j];
    			for(k=c;k<=min(d,j/i);k++)
    			{
    				f[i][j]=(f[i][j]+(f[i-1][j-i*k]*C(n-(j-i*k),i*k,p)%p)*g[i][k]%p)%p;
    			}
    		}
    	}
    	cout<<f[b][n]<<endl;
    	return 0;
    }
    

\(F\) P196. Pivot

  • 原题: [ARC152C] Pivot

  • 考虑数形结合,将 \(a_{i}\) 替换为 \(2s-a_{i}\) 等价于作 \(a_{i}\) 关于 \(s\) 的对称点 \(2s-a_{i}\)

  • 每次操作后,整个序列要么单调递增,要么单调递减,进而可以得到最大值仅会来自 \(1\)\(n\) 的位置,最小值同理;且任意两数之差不变。

  • 设第 \(i\) 次选择的数在原序列中对应的下标为 \(p_{i}\) ,容易有每次操作都会让 \(a_{1} \pm 2(a_{p_{i}}-a_{1})\) ,最终的 \(a_{1}'=a_{1}+\sum\limits_{i=1}^{m}2k_{i}(a_{p_{i}}-a_{1})\) ,其中 \(k_{i} \in \{ -1,1 \}\)

  • 考虑统计最小值,然后加上 \(a_{n}-a_{1}\) 即可得到最大值。

  • 由裴蜀定理, \(\gcd\limits_{i=1}^{k} \{ 2(a_{p_{i}}-a_{i}) \}|a_{1}'-a_{1}\) ,移项得 \(a_{1} \equiv a_{1}' \pmod{\gcd\limits_{i=1}^{k} \{ 2(a_{p_{i}}-a_{i}) \}}\)

  • \(\{ p \}\) 取遍 \([1,n]\) 时一定最优。

  • 同理取 \(a_{n}\) 计算 \(a_{n}'\)\(a_{1}'\) 即可。

    点击查看代码
    int a[200010];
    int gcd(int a,int b)
    {
    	return b?gcd(b,a%b):a;
    }
    int main()
    {
    	int n,d1=0,d2=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=1;i<=n;i++)
    	{
    		d1=gcd(d1,2*a[i]-2*a[1]);
    		d2=gcd(d2,2*a[n]-2*a[i]);
    	}
    	cout<<min(a[1]%d1,a[n]%d2)+a[n]-a[1]<<endl;
    	return 0;
    }
    

\(G\) P197. 11 : 23

  • @Delov 的可视化程序如下。

    点击查看代码
    #include <bits/stdc++.h>
    #include <termio.h>
    #include <unistd.h>
    typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
    #define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
    #define Rep(i,a,b) for(int i=a;i<=b;++i) 
    #define Dwn(i,a,b) for(int i=a;i>=b;--i)
    #define pii pair<int,int>
    #define mair make_pair
    #define fir first
    #define sec second
    using namespace std;
    
    const int maxn=1e5+10;
    
    int n,m;
    int S[20][20],D[20][20];
    int h[5],l[5],a[5],K,G;
    int V[maxn];
    int num[5];
    
    void Flush(){ cout<<"\033c";cout<<"\n\n\n\n\n\n\n\n\n\n\n\n"; }
    
    void Point(int x){ if(!x)cout<<"*      "; if(x==1)cout<<"\033[31mH"<<"\033[0m"<<"      "; if(x==2)cout<<"C      "; }
    void EdgeRow(int x){ if(x)cout<<"\033[32m"<<"R      "<<"\033[0m";else cout<<"-      "; }
    void EdgeCol(int x){ if(x)cout<<"\033[32m"<<"R    "<<"\033[0m";else cout<<"|    "; }
    
    int building[20][20];
    int pathrow[20][20],pathcol[20][20];
    
    void Print(int Tim){
    	Flush();
    	cout<<"DAYS "<<"  :: "<<Tim<<"\n";
    	Rep(i,Tim+1,52)cout<<V[i];
    	cout<<"\n\n\n\n";
    
    	cout<<"      ";
    	Rep(i,0,m)cout<<i<<"      ",(i && (cout<<i<<"      ",1));
    	cout<<endl;
    	cout<<0<<"     ";
    	Rep(i,0,m){ Point(building[0][i]);if(i<m)EdgeRow(pathrow[0][i]); }
    	cout<<endl<<endl;
    	Rep(i,1,n){
    		cout<<i<<"     ";EdgeCol(pathcol[i][0]);
    		Rep(j,1,m){
    			cout<<"["<<S[i][j]<<","<<D[i][j]<<"]    ";
    			EdgeCol(pathcol[i][j]);
    		}
    		cout<<endl<<endl;
    		cout<<i<<"     ";Rep(j,0,m){ Point(building[i][j]);if(j<m)EdgeRow(pathrow[i][j]); }
    		cout<<endl<<endl;
    	}
    	cout<<"you have       "<<" :: ";
    	Rep(i,1,4)cout<<"  "<<num[i]<<"  ";
    	cout<<endl<<endl;bool ok=0;
    	cout<<"build a house  "<<" :: ";Rep(i,1,4)cout<<"  "<<h[i]<<"  ";
    	ok=1;Rep(i,1,4)ok&=(num[i]>=h[i]);if(ok)cout<<" [OK] "<<"[B]";
    	cout<<endl;
    	cout<<"build a castle "<<" :: ";Rep(i,1,4)cout<<"  "<<h[i]+l[i]<<"  ";
    	ok=1;Rep(i,1,4)ok&=(num[i]>=h[i]+l[i]);if(ok)cout<<" [OK] "<<"[C]";
    	cout<<endl;
    	cout<<"Upgrate a house"<<" :: ";Rep(i,1,4)cout<<"  "<<l[i]<<"  ";
    	ok=1;Rep(i,1,4)ok&=(num[i]>=l[i]);if(ok)cout<<" [OK] "<<"[U]";
    	cout<<endl;
    	cout<<"build a path   "<<" :: ";Rep(i,1,4)cout<<"  "<<a[i]<<"  ";
    	ok=1;Rep(i,1,4)ok&=(num[i]>=a[i]);if(ok)cout<<" [OK] "<<"[R]";
    	cout<<endl;
    	cout<<"Exchange Cost  "<<" :: "<<"  "<<K;
    	cout<<endl;
    }
    
    vector<string>Tool;
    int Anssum=0;
    string Tos(int x){ stringstream Id;Id<<x;string res;Id>>res;return res; }
    
    void Assert(int tag){ if(!tag){ cout<<"Warning: Illegal Option\n"; } }
    bool Ill(){ cout<<"\033[31mWarning: Illegal Operation"<<"\033[0m"<<endl;usleep(500000);return true; }
    
    bool Option(int Tim){
    	string s;int x,y,xa,ya,xb,yb;
    
    	cin>>s;
    
    	if(s=="E")return Tool.emplace_back(s),false;
    	if(s=="B"){
    		cin>>x>>y;
    		if(building[x][y])return Ill();
    		if(Tim)Rep(i,1,4)if(num[i]<h[i])return Ill();
    		if(Tim)Rep(i,1,4)num[i]-=h[i];
    		building[x][y]=1;Anssum+=1;
    		s+=" "+Tos(x)+" "+Tos(y);
    	}
    	if(s=="C"){
    		cin>>x>>y;
    		if(building[x][y])return Ill();
    		Rep(i,1,4)if(num[i]<h[i]+l[i])return Ill();
    		Rep(i,1,4)num[i]-=h[i]+l[i];
    		building[x][y]=2;Anssum+=2;
    		s+=" "+Tos(x)+" "+Tos(y);
    	}
    	if(s=="U"){
    		cin>>x>>y;
    		if(building[x][y]!=1)return Ill();
    		Rep(i,1,4)if(num[i]<l[i])return Ill();
    		Rep(i,1,4)num[i]-=l[i];
    		building[x][y]=2;Anssum+=1;
    		s+=" "+Tos(x)+" "+Tos(y);
    	}
    	if(s=="R"){
    		cin>>xa>>ya>>xb>>yb;if(!((abs(xa-xb)==1 && ya==yb) || (abs(ya-yb)==1 && xa==xb)))return Ill();
    		Rep(i,1,4)if(num[i]<a[i])return Ill();
    		Rep(i,1,4)num[i]-=a[i];
    		if(xa==xb)pathrow[xa][min(ya,yb)]=1;
    		else pathcol[max(xa,xb)][ya]=1;
    		s+=" "+Tos(xa)+" "+Tos(ya)+" "+Tos(xb)+" "+Tos(yb);
    	}
    	if(s=="X"){
    		cin>>x>>y;if(num[x]<K)return Ill();
    		num[x]-=K,++num[y];
    		s+=" "+Tos(x)+" "+Tos(y);
    	}
    	Tool.emplace_back(s);
    	return true;
    }
    
    void Get(int x){
    	Rep(i,1,n)Rep(j,1,m)if(D[i][j]==x){
    		num[S[i][j]]+=(building[i-1][j-1]+building[i-1][j]+building[i][j-1]+building[i][j]);
    	}
    }
    
    void Check(int Tim){
    	if(Anssum>=10){
    		cout<<"\033c";
    		cout<<"\n\033[32mYou Win\n\n"<<"\033[0m";
    		cout<<Tim<<"\n";
    		for(auto it : Tool)cout<<it<<"\n";
    		exit(0);
    	}
    }
    
    void solve(){
    	cin>>n>>m;Rep(i,1,n)Rep(j,1,m)cin>>S[i][j];Rep(i,1,n)Rep(j,1,m)cin>>D[i][j];
    	Rep(i,1,4)cin>>h[i]; Rep(i,1,4)cin>>l[i]; Rep(i,1,4)cin>>a[i];
    	cin>>K>>G;Rep(i,1,G)cin>>V[i];
    
    	Print(0);
    	while(Option(0))Print(0);
    	Rep(i,1,G){ Get(V[i]);Print(i);while(Option(i))Print(i);Check(i); }
    	
    	cout<<Tool.size()<<"\n";
    	for(auto it : Tool)cout<<it<<"\n";
    }
    
    int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
    
    
  • @Delov 的可视化程序使用指南如下。

    点击查看使用指南

    编译,运行

    把输入文件粘进去,然后就会显示地图,地图下方是你有的各种资源及每种操作需要的资源、置换需要的资源。

    对于一种操作,如果你当前的资源足够,操作后面会显示 [OK] 及对应的操作字符

    按照题目格式输入操作即可。

    地图中 H 代表房屋 ,R 代表道路,C 代表城堡

    程序会在每轮操作结束时自动检查,如果已经完成任务就会自动结束并输出你的所有操作。

    如果地图显示过宽请自行在代码中寻找宽度设置或手动调整空格数量,或者修改终端的字体大小等,但显示器够大,应该没问题。

  • 高级题目,直接贺官方题解了。

\(H\) P207. 烙印融合 \(AC\)

  • 原题: [AGC005B] Minimum Sum

  • 不知道是第几次遇到这题了,直接贴自己 普及模拟1 T1 Past 第二问题解 了。

    点击查看代码
    ll a[3000001],f[3000001];
    stack<ll>s;
    int main()
    {
        ll n,ans=0,i;
        cin>>n; 
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(i=1;i<=n;i++)
        {
            while(s.empty()==0&&a[s.top()]>=a[i])
            {
                s.pop();
            }
            if(s.empty()==0)
            {
                f[i]=f[s.top()]+a[i]*(i-s.top());
            }
            else
            {
                f[i]=f[0]+a[i]*i;
            }
            s.push(i);
        }
        for(i=1;i<=n;i++)
        {
            ans+=f[i];
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(I\) P200. 魔术刻印

  • 人类智慧题目,直接贺官方题解了。

\(J\) T179. persona \(AC\)

  • 由于是区间推平,所以会比区间异或好做些。

  • 不难发现只有最后一次操作时取一段长度为 \(k\) 的子串一定最优,剩下位置全取正数即可。

  • 特判中间不取的情况。

    点击查看代码
    ll a[100010],sum[100010],l[100010],r[100010];
    int main()
    {
    	ll n,k,ans=0,i;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		l[i]=l[i-1]+(a[i]>0)*a[i];
    		sum[i]=sum[i-1]+a[i];
    	}
    	for(i=n;i>=1;i--)
    	{
    		r[i]=r[i+1]+(a[i]>0)*a[i];
    	}
    	for(i=1;i+k-1<=n;i++)
    	{
    		ans=max(ans,l[i-1]+max(0ll,sum[i+k-1]-sum[i-1])+r[i+k]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(K\) P194. 可持久化非确定性有穷状态决策自动机 \(AC\)

  • 现在 HZOI 构建了一个自动机,但很巧的是他只能接受一个长度为 8 的字符串。 猜测与 HZOI 有关。

  • 他是你们的某位学长的学长的学长的学长......\(bobo\) 所说 \(huge\) 是 HZ 毕业的,出自 [人生哲理]吕氏春秋 续集 2023.5.23 周二上课 。猜测和 \(huge\) 有关。

    • 所以我的第二发贡献给了 zhuwenhu
  • 他暑假的出场方式是回宿舍整改 要求内务比较严的只有 \(huge\)\(feifei\) 最多只是算“帮凶”, \(bobo\) 还在的时候根本不查内务,出自 高一上七月下旬日记 7.21 闲话 。基本确定和 \(huge\) 有关。

    • 所以我的第二发贡献给了 guxiaofei
  • 他还是想象学竞赛钻石级教练,你作为一名想象学竞赛选手对他非常崇拜想象学信息学 ,出自 @APJifengc第 41 届全国青少年想象学奥林匹克竞赛钻石级教师 没有找到出处,估计是对 \(huge\) 的设想(?)。

    • 真正能让我崇拜的只有 \(bobo\) 所以我的第一发给贡献给了 lvhongbo
  • 你想表达对他的爱,所以你打出了一句话 用拼音打出你想说的话说明答案和我、爱有关。

  • 一行一个长度为 8 的字符串,全小写字符。 综上所述, woaihuge 即为最终答案。

    • \(huge\) 拼音指虎哥,但常念作 /hjuːdʒ/ 而不是 hǔ'gē 。类似命名方法还有 \(guge(r)\)

    点击查看代码
    int main()
    {
        cout<<"woaihuge"<<endl;
        return 0;
    }
    

\(L\) T711. 随

  • 总方案数(分母)显然为 \(n^{m}\)

  • \(f_{k,i}\) 表示 \(k\) 次操作后 \(x\) 变成 \(i\) 的方案数,状态转移方程为 \(f_{k+1,i \times j \bmod mod}+=f_{k,i} \times cnt_{j}\) 。边界为 \(f_{1,i}=cnt_{i}\)

  • 最终有 \(\frac{\sum\limits_{i=0}^{mod-1}f_{m,i} \times i}{n^{m}}\) 即为所求。

  • 我们发现有 \(f_{k_{1}k_{2},i \times j \bmod mod}+=f_{k_{1},i} \times f_{k_{2},j}\) ,然后就可以倍增优化 \(DP\) 了。

    点击查看代码
    const ll p=1000000007;
    ll a[100010],cnt[100010],f[32][100010],g[32][100010];
    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 n,m,mod,x=0,y,len=0,i,j,k;
    	cin>>n>>m>>mod;
    	y=qpow(qpow(n,m,p),1000000005,p);
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		cnt[a[i]]++;
    	}
    	for(i=0;i<=mod-1;i++)
    	{
    		f[0][i]=cnt[i];
    	}
    	for(i=1;i<=31;i++)
    	{
    		for(j=0;j<=mod-1;j++)
    		{
    			for(k=0;k<=mod-1;k++)
    			{
    				f[i][j*k%mod]=(f[i][j*k%mod]+f[i-1][j]*f[i-1][k]%p)%p; 
    			}
    		}
    	}
    	for(i=31;i>=0;i--)
    	{
    		if((m>>i)&1)
    		{
    			len++;
    			if(len==1)
    			{
    				for(j=0;j<=mod-1;j++)
    				{
    					g[len][j]=f[i][j];
    				}
    			}   
    			else
    			{
    				for(j=0;j<=mod-1;j++)
    				{
    					for(k=0;k<=mod-1;k++)
    					{
    						g[len][j*k%mod]=(g[len][j*k%mod]+g[len-1][j]*f[i][k]%p)%p;
    					}
    				}
    			}
    		}
    	}
    	for(i=0;i<=mod-1;i++)
    	{
    		x=(x+g[len][i]*i%p)%p;
    	}
    	cout<<x*y%p<<endl;
    	return 0;
    }
    

总结

  • 赛时历程:开题后乱序开题。看到 \(K\) 的整活题,就猜了三发答案,没有头绪索性弃了。看到 \(H\) 后才意识到 \(A\) 是原题, @Pursuing_OIer 称他要打就让给他打了,就没给 @Charlie_ljk 说这是原题。 \(H\) 直接贺的 普及模拟1 T1 Past 代码,第一发没删取模结果过了,成功拿下首杀,魔芋爽加一。然后 @Delov\(L\)你想表达对他的爱 中的 加粗了,直接猜出答案,又拿下首杀,魔芋爽加一。然后就开始降智,尝试开 \(J\) 却无果。帮 @Charlie_ljk\(F\) ,又帮 @Pursuing_OIer\(A\) ,没什么结果。 @ccxswl@Pursuing_OIer 过掉了 \(A,J\) ,我在看 \(I,L\) 。他们开 \(B\) 时发现思路假了,问我哪个是可做题,我说 \(C,E,I,L\) 是计数 \(DP\)\(D\) 是概率期望 \(DP\)\(G\) 是提交答案加玩游戏题。索性四人都去做 \(F\) ,然后成功罚坐 \(3h\) ,荣获 \(95\) 发罚时。

后记

  • \(G\) 下发的 checker-linux 本地运行显示没有权限,赛时找 @Delov 在本地开了权限;题面是 JPG 形式,但是它死了,又给下发了 PDF 题面(将 JPG 拼起来);题面说有部分分存在,但由于是 \(ACM\) 赛制所以约等于摆设; @Delov 看我们都不写这题,还特意下发了可视化程序和使用指南,方便我们理解。 @Delov 称怕我们后三个小时没事就下发了个游戏让我们玩。
  • \(H\) 数据有点水,第一份代码没删对 \(1336363663\) 取模都过了。
  • \(H\)\(J\) 是因为组题人看签到题太少临时加的,并毙掉了 \(M\)\(N\) 的科技题。
  • \(K\) @Delov 本来想专门开个博客记录下都有什么炸裂的话,但看见三四个人向他表白后放弃了这个想法,不然的话我的 guxiaofei 就要上榜了。
posted @ 2024-08-02 20:02  hzoi_Shadow  阅读(121)  评论(8编辑  收藏  举报
扩大
缩小