高一上七月上旬日记

7.1

闲话

  • 早上起床后把行李拉到了机房楼下,时间还够顺便跑个操。
  • 上午 \(10:00\) 左右出了校园,将行李搬上货拉拉,让家长拉着我们去本部。我坐的是 @Pursuing_OIer 家长的车。进校前让给家里打个电话说下到本部的事情。
  • 进校后 \(miaomiao\) 领着去了机房,途中遇到了数奥教练。机房在科教馆,有电梯, \(miaomiao\) 说让我们尽量少坐电梯,免得碰上领导(附近领导多),翻译过来就是坐电梯的时候别碰见领导就行; \(miaomiao\) 说以前在 HS 的时候根本没人管,来了这里还得管领导。
  • 中午吃饭前 \(huge\) 说校服颜色不一样就不一样吧,我们低调点就行了。
  • 因高一晚上才分班,没有分宿舍,我们没宿舍住,遂中午吃完饭后来机房午休颓。
  • 机房设备较为简陋,包括但不限于左 Enter 键占了原来的左 Enter 键和反斜杠键,反斜杠键占了原退格键的一半,退格键只剩下了一个角,退格键因为自己笔记本键盘也差不多大小所以适应得快些;左 Ctrl 和 Tab 键按了之后会卡在下面,需要手动提上来;键盘右侧有关机键,按了之后 \(Linux\) 会有提示但 \(Windows\) 直接关机。 \(Linux\) 下火狐的默认搜索引擎是必应。
  • 被告知 \(12\) 个人都分到了尚勤楼 \(105\) ,正对着楼门,旁边就是楼管室,整栋楼只有初中奥赛的。没有垫子的让自己去“抢劫”垫子,顺便和楼管斗智斗勇。宿舍条件较差, \(8\) 个人的宿舍要住 \(12\) 个人,旧床是钉在墙上的,新床是用胶带绑在旧床上的,没有厕所和浴室,床下没有柜子,有风扇开关但没有风扇,窗户只能开一个角,每层楼有两个公共厕所和一个公共澡堂,没有饮水机和热水, \(huge\) 说他当时在 HZ 上学的时候就是这条件; @K8He 瑞平“西扩是天堂,HS 是屋子,旧 FY 是厕所,HZ 是屎”;因其他奥赛男生人少点,故尝试和 \(huge\) 交涉看能不能分成两个宿舍住;上床前被生奥教练 \(D\) 内务太差;熄灯后被楼管 \(D\) 称“咱们 HZ 这边要求晚上十点后一点声音都没有”。
  • 临时饭卡在宿舍能打电话,貌似是 \(miaomiao\) 找人开的权限。

做题纪要

牛客 NC275523 嘤嘤不想找最小喵

luogu P10676 『STA - R6』b20

  • 简单字符串。

    点击查看代码
    int main()
    {
    	string s1,s2;
    	cin>>s1>>s2;
    	cout<<s1[0]<<s2<<endl;
    	return 0;
    }
    

luogu P10677 『STA - R6』inkar-usi

  • 找到字典序最大的格子向四周扩展即可。

    点击查看代码
    int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
    char s[1010][1010];
    int main()
    {
    	int n,m,i,j,k;
    	char ans1=0,ans2=0;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			cin>>s[i][j];
    			if(s[i][j]!='#')
    			{
    				ans1=max(ans1,s[i][j]);
    			}
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			if(s[i][j]==ans1)
    			{
    				for(k=0;k<=3;k++)
    				{
    					if(1<=i+dir[k][0]&&i+dir[k][0]<=n&&1<=j+dir[k][1]&&j+dir[k][1]<=m&&s[i+dir[k][0]][j+dir[k][1]]!='#')
    					{
    						ans2=max(ans2,s[i+dir[k][0]][j+dir[k][1]]);
    					}
    				}
    			}
    		}
    	}
    	if(ans1==ans2||ans2==0)
    	{
    		cout<<ans1<<endl;
    	}
    	else
    	{
    		cout<<ans1<<ans2<<endl;
    	}
    	return 0;
    }
    

luogu P10679 『STA - R6』spec

  • 发扬人类智慧,有 \(\alpha \in [1,\min\limits_{i=1}^{n}\{ x_{i} \}+1]\)

  • 暴力枚举 \(\alpha\) ,此时若 \(x \in \operatorname{Spec}(\alpha)\) 则有 \(\left\lceil \left\lfloor\frac{x+1}{\alpha}\right\rfloor \times \alpha \right\rceil=x\)

  • 去重来优化复杂度。但复杂度是假的。

    点击查看hack数据
    in:2 999 1000
    out:1.9980040
    
  • 数据水罢了。

    点击查看代码
    int a[1010];
    int check(double x,int n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		if(ceil(floor(1.0*(a[i]+1)/x)*x)!=a[i]+1)
    		{
    			return 0;
    		}
    	}
    	return 1;
    }
    int main()
    {
    	int n,i;
    	double j;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	sort(a+1,a+1+n);
    	n=unique(a+1,a+1+n)-(a+1);
    	for(j=a[1]+1;j>=1;j-=0.0000005)
    	{
    		if(check(j,n)==1)
    		{
    			printf("%.7lf\n",j);
    			break;
    		}
    	}
    	return 0;
    }
    

luogu P3211 [HNOI2011] XOR和路径

  • luogu P3232 [HNOI2013] 游走 不同的是本题的边权无法直接相加,得到结果。

  • \(f_{i,k}\) 表示 \(i\)\(n\) 的异或和的第 \(k\) 位为 \(1\) 的概率,状态转移方程为 \(f_{i,k}=\sum\limits_{(i,j) \in E}\frac{[w_{i,j,k}=0] \times f_{j,k}+[w_{i,j,k}=1] \times (1-f_{j,k})}{du_{i}}\) ,移项得到 \(f_{i,k}-\sum\limits_{j=1}^{n-1}\frac{[(i,j) \in E] \times [w_{i,j,k}=0] \times f_{j,k}-[(i,j) \in E] \times [w_{i,j,k}=1] \times f_{j,k}}{du_{i}}=\sum\limits_{(i,j) \in E}[w_{i,j,k}=1]\) 。高斯消元解一下。

    • 注意 \(f_{n,k}=0\)
  • 最终,有 \(\sum\limits_{i=0}^{30}f_{1,i}\) 即为所求。

  • 重边要保留,自环只连一条边。

    点击查看代码
    const double eps=1e-10;
    struct node
    {
    	int nxt,to,w;
    }e[20010];
    int head[20010],du[20010],cnt=0;
    double a[110][110],f[110];
    void add(int u,int v,int w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void Gauss_Jordan(int n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		int val=i;
    		for(int j=i;j<=n;j++)
    		{
    			if(fabs(a[j][i])-fabs(a[val][i])>eps)
    			{
    				val=j;
    			}
    		}
    		for(int j=1;j<=n+1;j++)
    		{
    			swap(a[i][j],a[val][j]);
    		}
    		if(a[i][i]!=0)
    		{
    			for(int j=1;j<=n;j++)
    			{
    				if(j!=i)
    				{
    					for(int k=i+1;k<=n+1;k++)
    					{
    						a[j][k]-=a[i][k]*a[j][i]/a[i][i];
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	int n,m,u,v,w,i,j,k;	
    	double ans=0;
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v>>w;
    		add(u,v,w);
    		du[u]++;
    		if(u!=v)
    		{
    			add(v,u,w);
    			du[v]++;
    		}
    	}
    	for(i=0;i<=30;i++)
    	{
    		memset(a,0,sizeof(a));
    		for(j=1;j<=n-1;j++)
    		{
    			a[j][j]=1;
    			for(k=head[j];k!=0;k=e[k].nxt)
    			{
    				a[j][e[k].to]+=((e[k].w>>i)&1)?1.0*(e[k].to!=n)/du[j]:-1.0*(e[k].to!=n)/du[j];
    				a[j][n-1+1]+=((e[k].w>>i)&1)*1.0/du[j];
    			}
    		}
    		Gauss_Jordan(n-1);
    		for(j=1;j<=n-1;j++)
    		{
    			f[j]=a[j][n-1+1]/a[j][j];
    		}
    		ans+=(1<<i)*f[1];
    	}
    	printf("%.3lf\n",ans);
    	return 0;
    }
    

JZOJ 5814.树

  • \(u\)\(v\) 等价于从 \(u\)\(LCA(u,v)\) 再到 \(v\)

  • \(f_{x}\) 表示从 \(x\)\(fa_{x}\) 的期望步数, \(g_{x}\) 表示 \(fa_{x}\)\(x\) 的期望步数,有 \(\begin{cases} f_{x}=\frac{1}{du_{x}}+\sum\limits_{y \in Son(x)}\frac{1+f_{y}+f_{x}}{du_{x}} \\ g_{x}=\frac{1}{du_{fa_{x}}}+\frac{1+g_{fa_{x}}+g_{x}}{du_{fa_{x}}}+\sum\limits_{y \in Son(fa_{x})} \frac{[y \ne x] \times (1+g_{x}+f_{y})}{du_{fa_{x}}} \end{cases}\) ,移项得到状态转移方程 \(\begin{cases} f_{x}=du_{x}+\sum\limits_{y \in Son(x)}f_{y} \\ g_{x}=du_{fa_{x}}+g_{fa_{x}}+\sum\limits_{y \in Son(fa_{x})} [y \ne x] \times f_{y}=du_{fa_{x}}+g_{fa_{x}}-f_{x}+\sum\limits_{y \in Son(fa_{x})}f_{y}\end{cases}\) ,边界为 \(\begin{cases} f_{1}=1 \\ g_{1}=0 \end{cases}\)

  • 树上前缀和维护一下即可。

    点击查看代码
    const ll p=1000000007;
    struct node
    {
    	ll nxt,to;
    }e[200010];
    ll head[200010],siz[200010],fa[200010],dep[200010],son[200010],top[200010],du[200010],f[200010],g[200010],sumf[200010],sumg[200010],cnt=0;
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs1(ll x,ll father)
    {
    	f[x]=(x==1)?0:du[x];
    	siz[x]=1;
    	fa[x]=father;
    	dep[x]=dep[father]+1;
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dfs1(e[i].to,x);
    			f[x]+=(x!=1)*f[e[i].to];
    			siz[x]+=siz[e[i].to];
    			son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
    		}
    	}
    }
    void dfs2(ll x,ll father,ll id)
    {
    	top[x]=id;
    	if(son[x]!=0)
    	{
    		ll sum=0;
    		for(ll i=head[x];i!=0;i=e[i].nxt)
    		{
    			if(e[i].to!=father)
    			{
    				sum+=f[e[i].to];
    			}
    		}
    		for(ll i=head[x];i!=0;i=e[i].nxt)
    		{
    			if(e[i].to!=father)
    			{
    				g[e[i].to]=du[x]+g[x]+sum-f[e[i].to];
    			}
    		}
    		dfs2(son[x],x,id);
    		for(ll i=head[x];i!=0;i=e[i].nxt)
    		{
    			if(e[i].to!=father&&e[i].to!=son[x])
    			{
    				dfs2(e[i].to,x,e[i].to);
    			}
    		}
    	}
    }
    void dfs(ll x,ll father)
    {
    	sumf[x]+=f[x];
    	sumg[x]+=g[x];
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			sumf[e[i].to]+=sumf[x];
    			sumg[e[i].to]+=sumg[x];
    			dfs(e[i].to,x);
    		}
    	}
    }
    ll lca(ll u,ll v)
    {
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])
    		{
    			u=fa[top[u]];
    		}
    		else
    		{
    			v=fa[top[v]];
    		}
    	}
    	return (dep[u]<dep[v])?u:v;
    }
    int main()
    {
    	ll n,m,u,v,rt,i;
    	cin>>n>>m;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		add(v,u);
    		du[u]++;
    		du[v]++;
    	}
    	dfs1(1,0);
    	dfs2(1,0,1);
    	dfs(1,0);
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v;
    		rt=lca(u,v);
    		cout<<(sumf[u]-sumf[rt]+sumg[v]-sumg[rt])%p<<endl;
    	}
    	return 0;
    }
    

luogu P2602 [ZJOI2010] 数字计数

  • 多倍经验: UVA1640 统计问题 The Counting Problem | SP3928 MDIGITS - Counting Digits | [ABC029D] 1

  • 对于任意一个 \(i\) 位数(允许有前导零),所有数码的出现次数是相等的。设 \(f_{i}\) 表示所有 \(i\) 位数(允许有前导零)中每个数码的出现次数,状态转移方程为 \(f_{i}=10f_{i-1}+10^{i-2} \times 10=10f_{i-1}+10^{i-1}\)

  • 将最终答案拆成前缀和的形式,有 \(ans_{l,r}=ans_{1,r}-ans_{1,l-1}\) 。以下只讨论 \(ans_{1,r}\) 的求解。

  • 从高到低枚举 \(r\) 的每一位,设当前为第 \(i\) 位。当前要填的数 \(0 \le r'_{i}<r_{i}\) 时,后面几位可以任意取值,增加的贡献是 \(10^{i-1}\) ;要填的数 \(r'_{i}=r_{i}\) 时,贡献分成两部分,对 \(r_{i}\) 增加的贡献为 \(\overline{r_{i-1}r_{i-2} \dots r_{1}}+1\) ,对 \(0 \sim 9\) 增加的贡献为 \(r_{i}f_{i-1}\) ;特别地,当 \(r_{i}=0\) 且作为前导零时会额外多算 \(i-1\) 位填满的答案。

    点击查看代码
    ll ans[2][15],a[20],f[20],mi[20];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;    
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    void ask(ll n,ll ans[])
    {
    	ll len=divide(n,a),i,j;
    	for(i=len;i>=1;i--)
    	{
    		for(j=0;j<=a[i]-1;j++)
    		{
    			ans[j]+=mi[i-1];
    		}
    		for(j=0;j<=9;j++)
    		{
    			ans[j]+=a[i]*f[i-1];
    		}
    		n-=a[i]*mi[i-1];
    		ans[a[i]]+=n+1; 
    		ans[0]-=mi[i-1];
    	}
    }
    int main()
    {
    	ll l,r,i;
    	cin>>l>>r;
    	mi[0]=1;
    	for(i=1;i<=15;i++)
    	{
    		f[i]=10*f[i-1]+mi[i-1];
    		mi[i]=10*mi[i-1];
    	}
    	ask(r,ans[0]);
    	ask(l-1,ans[1]);
    	for(i=0;i<=9;i++)
    	{
    		cout<<ans[0][i]-ans[1][i]<<" ";
    	}
    	return 0;
    }
    

luogu P4999 烦人的数学作业

  • 多倍经验: luogu P1836 数页码 | SP17247 PR003004 - Digit Sum

  • 算出每个数码的出现次数,然后求和运算。

    点击查看代码
    const ll p=1000000007;
    ll ans[2][15],a[25],f[25],mi[25];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;    
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    void ask(ll n,ll ans[])
    {
    	ll len=divide(n,a),i,j;
    	for(i=len;i>=1;i--)
    	{
    		for(j=0;j<=a[i]-1;j++)
    		{
    			ans[j]+=mi[i-1];
    		}
    		for(j=0;j<=9;j++)
    		{
    			ans[j]+=a[i]*f[i-1];
    		}
    		n-=a[i]*mi[i-1];
    		ans[a[i]]+=n+1; 
    		ans[0]-=mi[i-1];
    	}
    }
    int main()
    {
    	ll t,l,r,sum,i,j;    
    	cin>>t;
    	mi[0]=1;
    	for(i=1;i<=20;i++)
    	{
    		f[i]=10*f[i-1]+mi[i-1];
    		mi[i]=10*mi[i-1];
    	}
    	for(j=1;j<=t;j++)
    	{
    		cin>>l>>r;
    		sum=0;
    		memset(ans,0,sizeof(ans));
    		ask(r,ans[0]);
    		ask(l-1,ans[1]);
    		for(i=0;i<=9;i++)
    		{
    			sum=(sum+((ans[0][i]-ans[1][i])%p)*i%p)%p;
    		}
    		cout<<sum<<endl;
    	}
    	return 0;
    }
    

牛客 NC51211 启示录

  • \(f_{i,0/1/2}\) 分别表示由 \(i\) 位数字(允许有前导零)构成的,开头已经有 \(0/1/2\) 个连续的 \(6\) 的非魔鬼数的个数, \(f_{i,3}\) 表示由 \(i\) 位数字(允许有前导零)构成的魔鬼数的个数,状态转移方程为 \(\begin{cases} f_{i,0}=9(f_{i-1,0}+f_{i-1,1}+f_{i-1,2}) \\ f_{i,1}=f_{i-1,0} \\ f_{i,2}=f_{i-1,1} \\ f_{i,3}=f_{i-1,2}+10f_{i-1,3} \end{cases}\) ,边界为 \(f_{0,0}=1\)

  • 通过“试填法”,从高到低给每一位填数,只要填了一个比上限小的数位,后面的数位可以随便填;当填的数等于上限时,才接着向后扫描。

    点击查看代码
    int f[25][4];
    int main()
    {
    	int t,n,len,rk,i,j,k,h,l;
    	cin>>t;
    	f[0][0]=1;
    	for(i=1;i<=20;i++)
    	{
    		f[i][0]=9*(f[i-1][0]+f[i-1][1]+f[i-1][2]);
    		f[i][1]=f[i-1][0];
    		f[i][2]=f[i-1][1];
    		f[i][3]=f[i-1][2]+10*f[i-1][3];
    	}
    	for(l=1;l<=t;l++)
    	{
    		cin>>n;
    		len=3;
    		while(f[len][3]<n)
    		{
    			len++;
    		}
    		for(i=len,k=0;i>=1;i--)//k表示已经试填的位中末尾6的个数
    		{
    			for(j=0;j<=9;j++)
    			{
    				rk=f[i-1][3];
    				if(j==6||k==3)
    				{
    					for(h=max(0,3-k-(j==6));h<=2;h++)//加上由于6导致的魔鬼数
    					{
    						rk+=f[i-1][h];
    					}
    				}
    				if(rk<n)
    				{
    					n-=rk;
    				}
    				else
    				{
    					k=(k<3)?(j==6)*(k+1):k;
    					cout<<j;
    					break;
    				}
    			}
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

7.2

闲话

  • 起床铃声音特别小,仍在下雨,下的雨还更大了。
  • 新高一的临时备课区占了旁边的两个机房(有电脑的屋子叫机房),见到了不少以前的老师,但现在就开始让老师们上班不是很理解。
  • 新高二以为早一是公自,来了趟机房有人把蓝牙鼠标和鼠标垫留在了机房,被 \(miaomiao\)\(feifei\) 发现了直接给收了,“不能惯着他们”。
  • 上午 \(huge\) 来做讲演。讲规范和奥赛生是荣誉而不是特权,不要当“刺头”时 \(D\) 了: @jijidawang 因放假前一天拒绝向 \(feifei\) 上交手机后选择回家;早上有新高二的迟到还态度恶劣,“我以后要天天迟到”;【数据删除】的事情让楼下上信息课的人知道了,幸亏学校没有严查;原 \(4\) 机房的众人装了 QQ 后四处水群。说以前的学长评价没过一年学校的管理就变得更松让我们不要觉得“这很帅”;信息的来源很多,脱离手机三年也没有太大影响,让我们少颓;他在 HZ 上学的时候体育课(怀疑应该是体活课)没有现在我们的多,大课间出去打球是他想都不敢想的;手机和 U 盘都是不被允许的;教练也在缓和和我们直接的关系,提到了教练间的管理有时不是很一致,如果出现这种情况私下里沟通,还有 \(feifei\) 仍按管新高二的模式来管我们让我们不是很适应,看来 \(huge\) 看我闲话了,先不要慌,毕竟 \(bobo\) 都能看《吕氏春秋》;让我们给新高二的做好示范,给他们点打击,让他们见识到“我们有多强,他们有多弱”;讲了下 HZOI2021 的高考成绩。
  • 临近中午吃饭的时候 \(miaomiao\) 让我们改变观念,不能再像之前进度不统一时一样了;再次强调当开新专题的时候,迅速结束掉手头在做的旧专题的最后一题,称题做得多没用,做够了就行;和 \(miaomiao\) 说了下最终让晚饭时间提前 \(5min\) ,午饭时间提前 \(5min\) 的事情让明天再试一天。
  • 下午 \(miaomiao\) 突然说了句“现在的进度还不算慢,还行”,称“机房的零食都姓于”。
  • 以为是今天查分,网站一会儿能上一会儿不能上,我还以为教练一会儿给打开,一会不让打开,晚上等了半天还没等到。

做题纪要

牛客 NC51212 月之谜

  • \(f_{i,j,p,r}\) 表示由 \(i\) 位数字(允许有前导零)且数码和为 \(j\) 的数值 \(\mod p=r\) 的数字的个数,状态转移方程为 \(f_{i,j,p,r}=\sum\limits_{k=0}^{\min(9,j)}f_{i-1,j-k,p,(r-k \times 10^{i-1}) \bmod p}\) ,边界为 \(f_{0,0,p,0}=1\)

  • 特别地,以下做法仅求出了 \([1,n)\) 的答案, \(n\) 的贡献需要单独贡献。

  • 进行试填,枚举数码总和 \(p\) ,设已经填了的数码和为 \(sum\) ,所构成的数值 \(\mod{p}\) 的余数为 \(r\) ,第 \(i\) 位要填的数为 \(j\) 。当 \(j<n_{i}\) 时增加的贡献为 \(f_{i-1,p-sum-j,p,(p-r-j \times 10^{i-1}) \bmod p}\) ;否则,令 \(\begin{cases} sum=sum+n_{i} \\ r=(r+j \times 10^{i-1}) \bmod p \end{cases}\)

  • 对于 \(l>r\) 的数据,理论上应该输出 \(0\) ,但数据要求输出负数。

    点击查看代码
    int f[15][110][110][110],mi[15],a[15];
    void init()
    {
    	mi[0]=1;
    	for(int i=1;i<=10;i++)
    	{
    		mi[i]=10*mi[i-1];
    	}
    	for(int p=1;p<=90;p++)
    	{
    		f[0][0][p][0]=1;
    		for(int i=1;i<=10;i++)
    		{
    			for(int j=0;j<=i*9;j++)
    			{
    				for(int r=0;r<=p-1;r++)
    				{
    					for(int k=0;k<=min(9,j);k++)
    					{
    						f[i][j][p][r]+=f[i-1][j-k][p][(r-k*mi[i-1]%p+p)%p];
    					}
    				}
    			}
    		}
    	}
    }
    int divide(int n,int a[])
    {
    	int len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    int ask(int n)
    {
    	if(n==0)
    	{
    		return 0;
    	}
    	else
    	{
    		int ans,num=0,len=divide(n,a);
    		for(int i=1;i<=len;i++)
    		{
    			num+=a[i];
    		}
    		ans=(n%num==0);
    		for(int p=1;p<=9*len;p++)
    		{
    			for(int i=len,sum=0,r=0;i>=1&&sum<=p;i--)
    			{
    				for(int j=0;j<a[i]&&p-sum-j>=0;j++)
    				{ 
    					ans+=f[i-1][p-sum-j][p][(p-(r+j*mi[i-1]%p)%p+p)%p];
    				}	
    				sum+=a[i];
    				r=(r+a[i]*mi[i-1]%p)%p;
    			}
    		}
    		return ans;
    	}
    }
    int main()
    {
    	int l,r;
    	init();
    	while(cin>>l>>r)
    	{
    		cout<<ask(r)-ask(l-1)<<endl;
    		//cout<<((l>r)?0:ask(r)-ask(l-1))<<endl;
    	}
    	return 0;
    }
    

luogu P4127 [AHOI2009] 同类分布

  • 多倍经验: [ABC336E] Digit Sum Divisible

  • 学校 \(OJ\) 上开了 \(2s\) ,但空间给到了 \(256MB\)

  • 数位 \(DP\) 记忆化搜索板子。

    • 记录当前位置(从左往右数) \(pos\) 、先前影响 \(pre\) 、当前答案 \(st\) 、前导零标记 \(lead\)\(0/1\) 分别代表无/有)、值域限制 \(limit\)\(0/1\) 分别代表无/有)。
    • \(pre\)\(st\) 的修改因题而异。
    • \(lead\) 修改
      • 此时 \(lead=1\) 且填的数为 \(0\) ,下一位的 \(lead=1\)
      • 此时 \(lead=1\) 且填的数不为 \(0\) ,下一位的 \(lead=0\)
      • 此时 \(lead=0\) ,下一位的 \(lead=0\)
    • \(limit\) 修改
      • 此时 \(limit=1\) 且填的数取到了上界,下一位的 \(limit=1\)
      • 此时 \(limit=1\) 且填的数没有取到了上界,下一位的 \(limit=0\)
      • 此时 \(limit=0\) ,下一位的 \(limit=0\)
    • 因转移状态不同,有 当 \(lead=0\)\(limit=0\) 时才能记录和使用 \(dp\) 值。
    • 搜索顺序会影响状态间的继承关系,进一步影响是否需要初始化。
    点击查看代码
    ll a[25],f[20][200][200];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre,ll st,ll limit,ll p)
    {
    	if(pos<=0)
    	{
    		return (pre==0)?0:(pre==p&&st==0);
    	}
    	if(f[pos][pre][st]!=-1&&limit==0)
    	{
    		return f[pos][pre][st];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,pre+i,(st*10%p+i)%p,(i==maxx)*limit,p);
    	}
    	return (limit==0)?f[pos][pre][st]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll len=divide(n,a),ans=0;
    	for(ll p=1;p<=len*9;p++)
    	{
    		memset(f,-1,sizeof(f));//因为有些情况下方案数为零但却是合法状态
    		ans+=dfs(len,0,0,1,p);//搜索顺序会影响状态间的继承关系
    	}
    	return ans;
    }	
    int main()
    {
    	ll l,r;
    	cin>>l>>r;
    	cout<<ask(r)-ask(l-1)<<endl;
    	return 0;
    }
    

luogu P2657 [SCOI2009] windy 数

  • 记忆化搜索,注意边界的处理。

    点击查看代码
    int a[15],f[15][15];
    int divide(int n,int a[])
    {
    	int len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    int dfs(int pos,int pre,int lead,int limit)
    {
    	if(pos<=0)
    	{
    		return 1;//搜到了
    	}
    	if(f[pos][pre]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre];
    	}
    	int ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		if(abs(pre-i)>=2)
    		{
    			if(i==0&&lead==1)
    			{
    				ans+=dfs(pos-1,-2,1,(i==maxx)*limit);
    			}
    			else
    			{
    				ans+=dfs(pos-1,i,0,(i==maxx)*limit);
    			}
    		}
    	}
    	return (lead==0&&limit==0)?f[pos][pre]=ans:ans;
    }
    int ask(int n)
    {
    	int len=divide(n,a);
    	memset(f,-1,sizeof(f));
    	return dfs(len,-2,1,1);
    }
    int main()
    {
    	int l,r;
    	cin>>l>>r;
    	cout<<ask(r)-ask(l-1)<<endl;
    	return 0;
    }
    

luogu P4317 花神的数论题

  • \(n\) 进行二进制分解,枚举二进制中 \(1\) 的个数,记忆化搜索含有这些 \(1\) 的个数。

    点击查看代码
    ll a[100],f[100][100];
    const ll p=10000007;
    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;
    }
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%2;
    		n/=2;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre,ll limit,ll sum)
    {
    	if(pos<=0)
    	{
    		return (pre==sum);
    	}
    	if(f[pos][pre]!=-1&&limit==0)
    	{
    		return f[pos][pre];
    	}
    	ll ans=0,maxx=(limit==0)?1:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,pre+(i==1),(i==maxx)*limit,sum);
    	}
    	return (limit==0)?f[pos][pre]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll ans=1,len=divide(n,a);
    	for(ll i=1;i<=50;i++)
    	{
    		memset(f,-1,sizeof(f));
    		ans=ans*qpow(i,dfs(len,0,1,i),p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll n;
    	cin>>n;
    	cout<<ask(n)<<endl;
    	return 0;
    }
    

luogu P6218 [USACO06NOV] Round Numbers S

  • 注意前导零对答案的影响。

    点击查看代码
    int a[35],f[35][35][35];
    int divide(int n,int a[])
    {
    	int len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%2;
    		n/=2;
    	}
    	return len;
    }
    int dfs(int pos,int pre0,int pre1,int lead,int limit)
    {
    	if(pos<=0)
    	{
    		return (pre0>=pre1);
    	}
    	if(f[pos][pre0][pre1]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre0][pre1];
    	}
    	int ans=0,maxx=(limit==0)?1:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,pre0+(i==0&&lead==0),pre1+(i==1),(i==0)*lead,(i==maxx)*limit);
    	}
    	return (lead==0&&limit==0)?f[pos][pre0][pre1]=ans:ans;
    }
    int ask(int n)
    {
    	int len=divide(n,a);
    	memset(f,-1,sizeof(f));
    	return dfs(len,0,0,1,1);
    }
    int main()
    {
    	int l,r;
    	cin>>l>>r;
    	cout<<ask(r)-ask(l-1)<<endl;
    	return 0;
    }
    

luogu P4124 [CQOI2016] 手机号码

  • 由于本题中手机号码一定是 \(11\) 位,故需要特判前导零。

    点击查看代码
    ll a[15],f[15][15][15][2][2][2];
    ll divide(ll n,ll a[])
    {	
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre_sum,ll pre_cnt,ll pre_cnt3,ll pre4,ll pre8,ll lead,ll limit)
    {
    	if(pos<=0)
    	{
    		return (pre_cnt3==1&&pre4+pre8<=1);
    	}
    	if(f[pos][pre_sum][pre_cnt][pre_cnt3][pre4][pre8]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre_sum][pre_cnt][pre_cnt3][pre4][pre8];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=(lead==1);i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,i,(i==pre_sum)*pre_cnt+1,pre_cnt3|((i==pre_sum)*pre_cnt+1>=3),pre4|(i==4),pre8|(i==8),(i==0)*lead,(i==maxx)*limit);
    	}
    	return (lead==0&&limit==0)?f[pos][pre_sum][pre_cnt][pre_cnt3][pre4][pre8]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll len=divide(max(n,10000000000ll),a);
    	memset(f,-1,sizeof(f));
    	return dfs(len,0,0,0,0,0,1,1)-(n<10000000000ll);
    }
    int main()
    {
    	ll l,r;
    	cin>>l>>r;
    	cout<<ask(r)-ask(l-1)<<endl;
    	return 0;
    }
    

CF55D Beautiful numbers

  • 多倍经验: SP8177 JZPEXT - Beautiful numbers EXTREME

  • 同余的传递性:若 \(\begin{cases} a,b \in \mathbf{Z} \\ p,q \in \mathbb{N}^{*} \\ q|p \end{cases}\) ,则当 \(a \equiv b \pmod{p}\) 时有 \(a \equiv b \pmod{q}\) 。故在本题中 \(\bmod\) 各非零数码均等于 \(0\) 等价于 \(\bmod\) 各非零数码的 \(\operatorname{lcm}\) 等于 \(0\) ,等价于 \(\mod 2520\) 后的结果 \(\bmod\) 各非零数码的 \(\operatorname{lcm}\) 等于 \(0\)

  • 同时,对于 \(S \subset \{1,2,3,4,5,6,7,8,9 \}\) ,有 \(\operatorname{lcm}_{x \in S} \{ x \}|2520\)

  • 所以我们可以预处理出 \(2520\) 的因数并离散化,分别记录当前位置、所构成的数字 \(\mod 2520\) 后的结果、非零数码的 \(\operatorname{lcm}\) ,接着就和正常的数位 DP 一样了。

  • 注意搜索顺序对答案继承的影响。

    点击查看代码
    ll a[25],g[2600],f[25][2600][55];
    ll gcd(ll a,ll b)
    {
    	return b?gcd(b,a%b):a;
    }
    ll lcm(ll a,ll b)
    {
    	return a/gcd(a,b)*b;
    }
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre_sum,ll pre_lcm,ll limit,ll p)
    {
    	if(pos<=0)
    	{
    		return (pre_sum%pre_lcm==0);
    	}
    	if(f[pos][pre_sum][g[pre_lcm]]!=-1&&limit==0)
    	{
    		return f[pos][pre_sum][g[pre_lcm]];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,(pre_sum*10%p+i)%p,(i==0)?pre_lcm:lcm(pre_lcm,i),(i==maxx)*limit,p);
    	}
    	return (limit==0)?f[pos][pre_sum][g[pre_lcm]]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll len=divide(n,a);
    	return dfs(len,0,1,1,2520);
    }
    void init()
    {
    	ll num=0;
    	memset(f,-1,sizeof(f));
    	for(ll i=1;i<=2520;i++)
    	{
    		if(2520%i==0)
    		{
    			num++;
    			g[i]=num;
    		}
    	}
    }
    int main()
    {
    	ll t,l,r,i;
    	cin>>t;
    	init();
    	for(i=1;i<=t;i++)
    	{
    		cin>>l>>r;
    		cout<<ask(r)-ask(l-1)<<endl;
    	}
    	return 0;
    }
    

HDU3652 B-number

  • 仅在开始时初始化。

    点击查看代码
    ll a[20],f[20][15][15][2];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre_sum,ll pre_last,ll pre13,ll lead,ll limit)
    {
    	if(pos<=0)
    	{
    		return pre_sum==0&&pre13==1;
    	}
    	if(f[pos][pre_sum][pre_last][pre13]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre_sum][pre_last][pre13];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,(pre_sum*10+i)%13,i,pre13|(pre_last==1&&i==3),(i==0)*lead,(i==maxx)*limit);
    	}
    	return (lead==0&&limit==0)?f[pos][pre_sum][pre_last][pre13]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll len=divide(n,a);
    	return dfs(len,0,0,0,1,1);
    }
    int main()
    {
    	ll n;
    	memset(f,-1,sizeof(f));
    	while(cin>>n)
    	{
    		cout<<ask(n)<<endl;
    	}
    	return 0;
    } 
    

AT_dp_s Digit Sum

  • 多倍经验: AT_tdpc_number 数

  • 注意减去 \(0\) 的贡献。

    点击查看代码
    const ll p=1000000007;
    ll a[10010],f[10010][110];
    char k[10010];
    ll dfs(ll pos,ll pre,ll lead,ll limit,ll d)
    {
    	if(pos<=0)
    	{
    		return (pre==0);
    	}
    	if(f[pos][pre]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans=(ans+dfs(pos-1,(pre+i)%d,(i==0)*lead,(i==maxx)*limit,d))%p;
    	}
    	return (lead==0&&limit==0)?f[pos][pre]=ans:ans;
    }
    ll ask(ll len,ll d)
    {
    	memset(f,-1,sizeof(f));
    	return dfs(len,0,1,1,d);
    }
    int main()
    {
    	ll d,len,i;
    	scanf("%lld%s",&d,k+1);
    	len=strlen(k+1);
    	reverse(k+1,k+1+len);
    	for(i=1;i<=len;i++)
    	{
    		a[i]=k[i]-'0';
    	}
    	printf("%lld\n",(ask(len,d)-1+p)%p);
    	return 0;
    }
    

HDU4734 F(x)

  • \(f_{i,j}\) 表示当前处理到第 \(i\) 位,剩下的位的权重为 \(j\) 的数的个数。

  • 不知道为啥需要把 return 0; 删了才能交上。

    点击查看代码
    ll a[20],f[20][10000],mi[40];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre,ll lead,ll limit)
    {
    	if(pre<0)
    	{
    		return 0;
    	}
    	if(pos<=0)
    	{
    		return (pre>=0);
    	}
    	if(f[pos][pre]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,pre-i*mi[pos-1],(i==0)*lead,(i==maxx)*limit);
    	}
    	return (lead==0&&limit==0)?f[pos][pre]=ans:ans;
    }
    ll ask(ll n,ll sum)
    {
    	ll len=divide(n,a);	
    	return dfs(len,sum,1,1);
    }
    int main()
    {
    	ll t,l,r,sum,i,j;
    	cin>>t;
    	mi[0]=1;
    	for(i=1;i<=32;i++)
    	{
    		mi[i]=mi[i-1]*2;
    	}
    	memset(f,-1,sizeof(f));
    	for(i=1;i<=t;i++)
    	{
    		cin>>l>>r;
    		sum=0;
    		for(j=1;j<=divide(l,a);j++)
    		{
    			sum+=a[j]*mi[j-1];
    		}
    		cout<<"Case #"<<i<<": "<<ask(r,sum)<<endl;
    	}
    	//return 0;
    }
    

7.3

闲话

  • 早饭前 \(field\) 又通知我们早饭时间从 \(7:00\) 改成了 \(6:45\)
  • 吃完早饭后 \(huge\) 突然进来说让我们回去收拾内务进行“放风”
  • 上午因一些原因开了全网, @wang54321 因好奇心害死猫上了【数据删除】,在 @lty_ylzsx (拔掉鼠标线、键盘线) 和 @wkh2008 (按住四肢)等人的助攻下被 \(field\) 抓了,还拍了拍 @wang54321 的肩膀。
  • 中午宿管亲自来我们宿舍 \(D\) @STA_Morlin 早起没叠被。
  • 下午放了计数 DP 的视频。

做题纪要

luogu T467017 [CL-13] CTH: 谁帮我切开这个蛋糕???

  • \(f_{i}\) 表示当前处理到第 \(i\) 位的方案数,状态转移方程为 \(f_{i}=\begin{cases} \sum\limits_{j=i+1}^{n+1} f_{j} & s_{i}=1 \\ f_{i+1} & s_{i}=0 \end{cases}\) ,边界为 \(f_{n+1}=1\)

  • 需要前缀和优化。

    点击查看代码
    const ll p=998244353;
    ll f[10000010],sum[10000010];
    char s[10000010];
    int main()
    {
    	ll n,i;
    	cin>>n>>(s+1);
    	f[n+1]=sum[n+1]=1;
    	for(i=n;i>=1;i--)
    	{
    		f[i]=(s[i]=='1')?sum[i+1]:f[i+1];
    		sum[i]=(sum[i+1]+f[i])%p;
    	}
    	cout<<f[1]<<endl;
    	return 0;
    }
    

tgHZOJ 2761.苍与红的试炼

  • 由于搜索没有上限,所以 \(DFS\) 会一直递归下去爆栈,考虑对 \(BFS\) 进行记忆化。

    点击查看代码
    ll vis[5010][510],path[2500010],ans[2500010];
    void print(ll x)
    {
    	if(x==0)
    	{
    		return;
    	}
    	else
    	{
    		print(path[x]);
    		cout<<ans[x];
    	}
    }
    void bfs(ll s,ll d)
    {
    	int sum,r,nsum,nr,cnt,len,i;
    	queue<pair<int,int> >q;
    	q.push(make_pair(0,0));
    	vis[0][0]=1;
    	for(cnt=0,len=0;q.empty()==0;len++)
    	{
    		sum=q.front().first;
    		r=q.front().second;
    		q.pop();
    		if(nsum==s&&nr==0)
    		{
    			print(cnt);
    			return;
    		}
    		for(i=0;i<=min(9ll,s-sum);i++)
    		{	
    			nsum=sum+i;
    			nr=(r*10+i)%d;
    			if(vis[nsum][nr]==0)
    			{
    				vis[nsum][nr]=1;
    				cnt++;
    				ans[cnt]=i;
    				path[cnt]=len;
    				q.push(make_pair(nsum,nr));
    			}
    		}
    	}
    	cout<<-1<<endl;
    }
    void ask(ll s,ll d)
    {
    	bfs(s,d);
    }
    int main()
    {
    	ll s,d;
    	cin>>d>>s;
    	ask(s,d);
    	return 0;
    }
    

luogu P2518 [HAOI2010] 计数

  • 考虑进行试填,类似康托展开,找到一位使得填的数小于原数这一位,后面的方案数为 \(\dfrac{(\sum\limits_{i=0}^{9}cnt_{i})!}{\prod\limits_{i=0}^{9}cnt_{i}!}\) ,其中 \(cnt_{i}\) 表示 \(i\) 的出现次数。

  • 现在问题来到了如何求 \(\frac{(\sum\limits_{i=0}^{9}cnt_{i})!}{\prod\limits_{i=0}^{9}cnt_{i}!}\)

    • __int128_t 冲过去。
      • 分母、分子分别分解质因数,再乘起来。
      • 将式子拆成 \(\begin{aligned} \dfrac{(\sum\limits_{i=0}^{9}cnt_{i})!}{\prod\limits_{i=0}^{9}cnt_{i}!}=\prod\limits_{i=0}^{9}\dbinom{\sum\limits_{j=i}^{9}cnt_{j}}{cnt_{i}} \end{aligned}\) ,然后计算组合数来算。
      • 由于答案 \(<2^{63}-1\) ,故答案在模 \(\ge 2^{63}\) 的质数意义下仍等于原数。故可以找一个 \(\ge 2^{63}\) 的质数求逆元计算。通过 WolframAlpha 我们可以找到 \(9223372036854775837\) 是符合题意的。
    • python 大法好。
    点击查看代码
    ll a[60],cnt[60],inv[60],jc[60],jc_inv[60];
    char n[60];
    void write(ll x)
    {
    	if(x<0)
    	{
    		putchar('-');
    		x=-x;
    	}
    	if(x>9)
    	{
    		write(x/10);
    	}
    	putchar((x%10)+'0');
    }
    int main()
    {
    	ll len,ans=0,sum,p=9000000000000000000,i,j,k;
    	p+=223372036854775837;
    	cin>>(n+1);
    	len=strlen(n+1);
    	reverse(n+1,n+1+len);
    	for(i=1;i<=len;i++)
    	{
    		a[i]=n[i]-'0';
    		cnt[a[i]]++;
    	}
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=len;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=len;i>=1;i--)
    	{
    		for(j=0;j<=a[i]-1;j++)
    		{
    			if(cnt[j]!=0)
    			{
    				sum=0;
    				for(k=0;k<=9;k++)
    				{
    					sum+=cnt[k]-(j==k);
    				}
    				sum=jc[sum];
    				for(k=0;k<=9;k++)
    				{
    					sum=sum*jc_inv[cnt[k]-(j==k)]%p;
    				}
    				ans=(ans+sum)%p;
    			}
    		}
    		cnt[a[i]]--;
    	}
    	write(ans);
    	return 0;
    }
    

SP7155 CF25E - Test

  • 多倍经验: CF25E Test

    点击查看代码
    int nxt[400010],f[(1<<5)+10][5],g[5][5];
    string s[5],ss[5],t;
    int main()
    {
        int m,n,ans,flag,i,j,k,x,y;
        while(cin>>ss[0]>>ss[1]>>ss[2])
        {
            m=3;
            n=0;
            ans=0x7f7f7f7f;
            memset(f,0x3f,sizeof(f));
            sort(ss+0,ss+m);
            m=unique(ss+0,ss+m)-ss;
            for(i=0;i<=m-1;i++)
            {
                flag=0;
                for(j=0;j<=m-1;j++)
                {
                    if(i!=j&&ss[j].find(ss[i])!=string::npos)
                    {
                        flag=1;
                        break;
                    }
                }
                if(flag==0)
                {
                    s[n]=ss[i];
                    n++;
                }
            }
            for(x=0;x<=n-1;x++)
            {
                for(y=0;y<=n-1;y++)
                {
                    if(x!=y)
                    {
                        t=' '+s[y]+'#'+s[x];
                        for(i=2,nxt[1]=j=0;i<t.size();i++)
                        {
                            while(j>=1&&t[i]!=t[j+1])
                            {
                                j=nxt[j];
                            }
                            j+=(t[i]==t[j+1]);
                            nxt[i]=j;
                        }
                        g[x][y]=nxt[t.size()-1];
                    }
                }
            }
            for(i=0;i<=n-1;i++)
            {
                f[(1<<i)][i]=s[i].size();
            }
            for(i=0;i<=(1<<n)-1;i++)
            {
                for(j=0;j<=n-1;j++)
                {
                    if((i>>j)&1)
                    {
                        for(k=0;k<=n-1;k++)
                        {
                            if(j!=k&&((i>>k)&1))
                            {
                                f[i][j]=min(f[i][j],f[i-(1<<j)][k]-g[k][j]+(int)s[j].size());
                            }
                        }
                    }
                }
            }
            for(i=0;i<=n-1;i++)
            {
                ans=min(ans,f[(1<<n)-1][i]);
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

luogu P4109 [HEOI2015] 定价

  • 在末尾 \(0\) 尽可能多的情况下在前面放 \(5\)

  • 枚举 \(0\) 的位数接着一位位加(增长速度越快)。

    点击查看代码
    int main()
    {
    	int t,l,r,x,p,len,lenn,ans,minn,i;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>l>>r;
    		minn=0x7f7f7f7f;
    		ans=0x7f7f7f7f;
    		while(l<=r)
    		{
    			x=l;
    			len=0;
    			while(x%10==0)
    			{
    				len++;
    				x/=10;
    			}
    			lenn=log10(x)+1;
    			p=2*lenn-(x%10==5);
    			if(minn>p)
    			{
    				minn=min(minn,p);
    				ans=l;
    			}
    			l+=pow(10,len);
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

luogu P3311 [SDOI2014] 数数

  • 建立 \(AC\) 自动机来加速字符串匹配。

  • 注意减去 \(0\) 的贡献。

    点击查看代码
    const ll p=1000000007;
    ll a[2000],f[2000][2000];
    char n[2000],s[2000];
    struct ACM
    {   
    	int trie[2000][15],fail[2000],vis[2000],tot=0;
    	int val(char x)
    	{
    		return x-'0';
    	}
    	void insert(char s[],int len)
    	{
    		int x=0,i;
    		for(i=1;i<=len;i++)
    		{
    			if(trie[x][val(s[i])]==0)
    			{
    				tot++;
    				trie[x][val(s[i])]=tot;
    			}
    			x=trie[x][val(s[i])];
    		}
    		vis[x]=1;
    	}
    	void build()
    	{
    		int x,i;
    		queue<int>q;
    		for(i=0;i<=9;i++)
    		{
    			if(trie[0][i]!=0)
    			{
    				fail[trie[0][i]]=0;
    				q.push(trie[0][i]);
    			}
    		}
    		while(q.empty()==0)
    		{
    			x=q.front();
    			q.pop();
    			for(i=0;i<=9;i++)
    			{
    				if(trie[x][i]==0)
    				{
    					trie[x][i]=trie[fail[x]][i];
    				}
    				else
    				{
    					fail[trie[x][i]]=trie[fail[x]][i];
    					vis[trie[x][i]]|=vis[fail[trie[x][i]]];
    					q.push(trie[x][i]);
    				}
    			}
    		}
    	}
    }T;
    ll dfs(ll pos,ll pre,ll lead,ll limit)
    {
    	if(pos<=0)
    	{
    		return (T.vis[pre]==0);
    	}
    	if(T.vis[pre]==1)
    	{
    		return 0;
    	}
    	if(f[pos][pre]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre];
    	}    
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans=(ans+dfs(pos-1,((i==0)*lead)?0:T.trie[pre][i],(i==0)*lead,(i==maxx)*limit))%p;  
    	}
    	return (lead==0&&limit==0)?f[pos][pre]=ans:ans;
    }
    ll ask(ll len)
    {
    	memset(f,-1,sizeof(f));
    	return dfs(len,0,1,1);;
    }
    int main()
    {
    	ll len,m,i;
    	cin>>(n+1)>>m;
    	len=strlen(n+1);
    	for(i=1;i<=m;i++)
    	{
    		cin>>(s+1);
    		T.insert(s,strlen(s+1));
    	}
    	T.build();
    	reverse(n+1,n+1+len);
    	for(i=1;i<=len;i++)
    	{
    		a[i]=n[i]-'0';
    	}
    	cout<<(ask(len)-1+p)%p<<endl;
    	return 0;
    }
    

BZOJ3329 Xorequ

  • 移项,有 \(x \bigoplus (2x)=3x=x+2x\) ,即 \(x+2x\) 的过程中在二进制表示下不存在进位,故原方程成立当且仅当 \(x\) 在二进制表示下没有连续的 \(1\)

  • 第一问当做普通的数位 \(DP\) 来处理。

  • 第二问,即 不相邻的排列 ,考虑将其转化为 \(n\) 个座位上要坐人,每个人坐一个座位,要求每两个人直接必须隔至少一个空,则增加 \(1\) 个座位,令每个人坐两个座位,其方案数为 \(\sum\limits_{i=0}^{n}\dbinom{n+1-i}{i}=Fib_{n+2}\)

    点击查看代码
    const ll p=1000000007;
    struct Matrix
    {
    	ll ma[5][5];
    	Matrix()
    	{
    		memset(ma,0,sizeof(ma));
    	}
    }f,a;
    ll b[70],dp[70][2][2];
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
    	Matrix c;
    	for(ll i=1;i<=n;i++)
    	{
    		for(ll j=1;j<=k;j++)
    		{
    			for(ll h=1;h<=m;h++)
    			{
    				c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
    			}
    		}
    	}
    	return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
    	Matrix ans;
    	for(ll i=1;i<=n;i++)
    	{
    		ans.ma[i][i]=1;
    	}
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=mul(ans,a,n,n,n,p);
    		}
    		b>>=1;
    		a=mul(a,a,n,n,n,p);
    	}
    	return ans;
    }
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%2;
    		n/=2;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre1,ll pre11,ll lead,ll limit)
    {
    	if(pos<=0)
    	{
    		return (pre11==0);
    	}
    	if(pre11==1)
    	{
    		return 0;
    	}
    	if(dp[pos][pre1][pre11]!=-1&&lead==0&&limit==0)
    	{
    		return dp[pos][pre1][pre11];
    	}
    	ll ans=0,maxx=(limit==0)?1:b[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,(i==1),pre11|((i==1)*pre1),(i==0)*lead,(i==maxx)*limit);
    	}
    	return (lead==0&&limit==0)?dp[pos][pre1][pre11]=ans:ans;
    }
    ll ask1(ll n)
    {
    	ll len=divide(n,b);
    	return dfs(len,0,0,1,1);
    }
    ll ask2(ll b)
    {
    	ll n=1,m=2,k=2;
    	f.ma[1][1]=0;
    	f.ma[1][2]=1;
    	a.ma[1][1]=0;
    	a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
    	return mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][1];
    }
    int main()
    {
    	ll t,n,i;
    	cin>>t;
    	memset(dp,-1,sizeof(dp));
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		cout<<ask1(n)-1<<endl;
    		cout<<ask2(n+2)<<endl;
    	}
    	return 0;
    } 
    

HDU4352 XHXJ's LIS

  • \(f_{i,s,j}\) 表示当前处理到第 \(i\) 位,数码在所有的长度为 \(len\) 的上升子序列(如果存在的话)的末尾元素的最小值中出现的状态为 \(s\) ,且要求最终最长上升子序列长度 \(=j\) 的方案数。状态 \(s\) 的更新同 算法二 中的 \(d\) 的更新。

    点击查看代码
    ll a[25],f[25][(1<<13)+10][13];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll update(ll s,ll x)
    {
    	for(ll i=x;i<=9;i++)
    	{
    		if((s>>i)&1)
    		{
    			s-=1<<i;
    			break;
    		}
    	}
    	return (s|(1<<x));
    }
    ll dfs(ll pos,ll pre,ll lead,ll limit,ll k)
    {
    	if(pos<=0)
    	{
    		return (__builtin_popcount(pre)==k);
    	} 
    	if(f[pos][pre][k]!=-1&&lead==0&&limit==0)
    	{
    		return f[pos][pre][k];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,((i==0)*lead)?0:update(pre,i),(i==0)*lead,(i==maxx)*limit,k);
    	}
    	return (lead==0&&limit==0)?f[pos][pre][k]=ans:ans;
    }
    ll ask(ll n,ll k)
    {   
    	ll len=divide(n,a);
    	return dfs(len,0,1,1,k);
    }
    int main()
    {
    	ll t,l,r,k,i;
    	cin>>t;
    	memset(f,-1,sizeof(f));
    	for(i=1;i<=t;i++)
    	{
    		cin>>l>>r>>k;
    		cout<<"Case #"<<i<<": "<<ask(r,k)-ask(l-1,k)<<endl;
    	}
    	//return 0;
    }
    

CF559C Gerald and Giant Chess

  • 多倍经验: AT_dp_y Grid 2

  • 正难则反,考虑求出总方案数和至少经过一个黑色格子的方案数,二者作差即为所求。

  • 强制增加一个黑色格子 \((h,w)\) ,使得存在一条至少经过一个黑色格子的路径。

  • 如果没有“不能移动到黑色格子中”的限制,那么就是一个简单的格路计数问题,方案数为 \(\dbinom{h+w-2}{h-1}\)

  • 对黑色格子以行坐标为第一关键字,列坐标为第二关键字升序排序。

  • \(f_{i}\) 表示从左上角到排序后的第 \(i\) 个黑色格子的方案数,状态转移方程为 \(f_{i}=\dbinom{x_{i}+y_{i}-2}{x_{i}-1}-\sum\limits_{j=1}^{i-1}[x_{i} \ge x_{j}] \times [y_{i} \ge y_{j}] \times f_{j} \times \dbinom{x_{i}-x_{j}+y_{i}-y_{j}}{x_{i}-x_{j}}\)

  • 最终,有 \(f_{n+1}\) 即为所求。

    点击查看代码
    const ll p=1000000007;
    ll f[2010],inv[200010],jc[200010],jc_inv[200010];
    pair<ll,ll>a[2010];
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0;
    }
    int main()
    {
    	ll h,w,n,i,j;  
    	cin>>h>>w>>n;
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=h+w-2;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++)
    	{
    		cin>>a[i].first>>a[i].second;
    	}
    	n++;
    	a[n]=make_pair(h,w);
    	sort(a+1,a+1+n);
    	for(i=1;i<=n+1;i++)
    	{
    		f[i]=C(a[i].first+a[i].second-2,a[i].first-1,p);
    		for(j=1;j<=i-1;j++)
    		{
    			if(a[j].first<=a[i].first&&a[j].second<=a[i].second)
    			{
    				f[i]=(f[i]-f[j]*C(a[i].first-a[j].first+a[i].second-a[j].second,a[i].first-a[j].first,p)%p+p)%p;
    			}
    		}
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

7.4

闲话

  • 早上 \(6:45\) 出机房去吃饭的时候 \(huge\) 没说啥,但到了食堂有食堂工作人员把我们拦住了,问我们为啥来得这么早,然后他给别人打电话问新高一是不是应该现在吃饭。
  • 吃完早饭 \(huge\) 又把我们叫回去说要整理内务,说可以有 \(4\) 个人搬到仅有 \(3\) 个人的 \(101\) 室(数奥宿舍),回去后发现是 @STA_Morlin 没叠被子, @STA_Morlin@Pursuing_OIer 的床下垃圾太明显。\(huge\) 监督我们收拾完后就回机房了,最后还说了句“这一道题的时间不就浪费出去了”。去和回来的过程中都要求跑着。
  • 回到机房后 \(miaomiao\) 又进来说让我们收拾好内务,之前在 HS 的时候因为是初中,而且是单独住,所以没有人管内务,但现在上高一了,有其他奥赛教练来查宿。 \(miaomiao\) 说让上午结掉计数 \(DP\) ,下午和晚上补一下概率期望,明天一次性把优化 \(DP\) 开完。
  • 上午 \(10:27\) 左右 \(miaomiao\) 突然进来说让查一下中考分,他好统计,他刚说完我就说页面进不去,显示连接已重置, @xrlong 瑞平为“一看就知道是早有准备”。用 @HANGRY_sol 电脑查了下中考分,语文 \(111pts\) ,数学 \(115pts\) ,英语 \(117pts\) ,理综 \(108pts\) ,文综 \(119pts\) ,理化实验 \(10pts\) ,信息技术 \(10pts\) ,体育 \(47pts\) ,总分 \(637pts\) ,一分一档上看着是主城区 \(rk723\) ,上 HS 普惠没问题,但上公助有点悬。最终,算上 @K8He 是机房倒第 \(3\) ,理综是机房最低分。 \(huge\) 说我们不要因为中考考好了太嘚瑟。
  • 中午和家长打了个电话,没考过两个小学同学,家里没说啥; @lty_ylzsx 徒手把宿舍限位器拆了,被称为“扳手哥”; \(huge\) 查宿。
  • 白天在必应上看到一篇 唐文 ,这要是让百家号/营销号知道我们也用 \(\LaTeX\) 是不是也应该说“OIer在中学里面学信奥,从未写过论文,他们为啥要熟练掌握 \(\LaTeX\) ?”和“作为一个中学生,有多少机会坐在电脑前使用如此耗时耗力而不直接促进数学水平的 \(\LaTeX\) ?”,纯唐,和 满分!衡水一中学学生拿全国第一!班里有牛娃咋办 没事别跟牛娃比 一个性质。
  • 下午第 \(9,10\) 节课课件 \(miaomiao\) 突然进来让我们去西操场练跑操,然后自由活动,算是上体活了。到了西操场,数奥教练称我们都已经跑了三年操了,还练跑操没什么意义,所以就只让跑了一圈。自由活动时 @K8He 带着我们在校园里面乱转,体验到了 HZ 的物种多样性和学生全面发展,原来项目性学习不是梦。然后去了趟超市,买了个指甲刀和一包纸。
  • 吃完晚饭,本来报修的是空调冷凝管滴水,结果顺便给窗户安了两个限位器,纱窗也安上了。
  • 晚上 @STA_Morlin 因“口无遮拦”,被 \(huge\) \(D\) 让其改改口头禅; \(huge\) 查宿, \(21:51\) 就说让我们准备上床,我和他说平常这个时候我们才刚回到宿舍,他没说啥。

做题纪要

牛客 NC51209 Connected Graph

  • 超级加强版: luogu P4841 [集训队作业2013] 城市规划

  • 正难则反,考虑求出总方案数和不连通无向图的数量。

  • 总方案数为 \(2^{\frac{n(n-1)}{2}}\)

  • 将不连通无向图拆成多个连通块。设 \(f_{i}\) 表示 \(i\) 个节点的无向连通图个数,状态转移方程为 \(f_{i}=2^{\frac{i(i-1)}{2}}-\sum\limits_{j=1}^{i-1}f_{j} \times \dbinom{i-1}{j-1} \times 2^{\frac{(i-j)(i-j-1)}{2}}\) ,边界为 \(f_{1}=1\)

  • 最终,有 \(f_{n}\) 即为所求。

  • 高精度部分来自 @xrlong板子

    点击查看代码
    namespace BIG_num{
    using cpx=complex<double>;const double PI=acos(-1);vector<cpx>roots= {{0,0},{1,0}};
    void ensure_capacity(int min_capacity) {
    	for(int len=roots.size(); len<min_capacity; len*=2) {for(int i=len>>1; i<len; i++) {
    		roots.emplace_back(roots[i]);double angle=2*PI*(2*i+1-len)/(len*2);roots.emplace_back(cos(angle),sin(angle));
    	}}
    }
    void fft(vector<cpx>&z,bool inverse) {
    	int n=z.size();ensure_capacity(n);
    	for(int i=1,j=0; i<n; i++) {int BIGt=n>>1;for(; j>=BIGt; BIGt>>=1)j-=BIGt;j+=BIGt;if(i<j)swap(z[i],z[j]);}
    	for(int len=1; len<n; len<<=1) {for(int i=0; i<n; i+=len*2) {for(int j=0; j<len; j++) {
    		cpx root=inverse?conj(roots[j+len]):roots[j+len];cpx u=z[i+j];cpx v=z[i+j+len]*root;z[i+j]=u+v;z[i+j+len]=u-v;
    	}}}if(inverse)for(int i=0; i<n; i++)z[i]/=n;
    }
    vector<int>multiply_BIG(const vector<int>&a,const vector<int>&b,int base) {
    	int need=a.size()+b.size();int n=1;while(n<need)n<<=1;vector<cpx>p(n);
    	for(int i=0; i<n; i++) {p[i]=cpx(i<(int)a.size()?a[i]:0,i<(int)b.size()?b[i]:0);}
    	fft(p,false);vector<cpx>ab(n);cpx r(0,-0.25);
    	for(int i=0; i<n; i++) {int j=(n-i)&(n-1);ab[i]=(p[i]*p[i]-conj(p[j]*p[j]))*r;}
    	fft(ab,true);vector<int>result(need);long long carry=0;
    	for(int i=0; i<need; i++) {long long d=(long long)(ab[i].real()+0.5)+carry;carry=d/base;result[i]=d%base;}
    	return result;
    }
    vector<int>multiply_mod(const vector<int>&a,const vector<int>&b,int m) {
    	int need=a.size()+b.size()-1;int n=1;while(n<need)n<<=1;vector<cpx>A(n);
    	for(size_t i=0; i<a.size(); i++) {int x=(a[i]%m+m)%m;A[i]=cpx(x&((1<<15)-1),x>>15);}
    	fft(A,false);vector<cpx>B(n);
    	for(size_t i=0; i<b.size(); i++) {int x=(b[i]%m+m)%m;B[i]=cpx(x&((1<<15)-1),x>>15);}
    	fft(B,false);vector<cpx>fa(n);vector<cpx>fb(n);
    	for(int i=0,j=0; i<n; i++,j=n-i) {
    		cpx a1=(A[i]+conj(A[j]))*cpx(0.5,0);cpx a2=(A[i]-conj(A[j]))*cpx(0,-0.5);
    		cpx b1=(B[i]+conj(B[j]))*cpx(0.5,0);cpx b2=(B[i]-conj(B[j]))*cpx(0,-0.5);
    		fa[i]=a1*b1+a2*b2*cpx(0,1);fb[i]=a1*b2+a2*b1;
    	}fft(fa,true);fft(fb,true);vector<int>res(need);
    	for(int i=0; i<need; i++) {
    		long long aa=(long long)(fa[i].real()+0.5);long long bb=(long long)(fb[i].real()+0.5);
    		long long cc=(long long)(fa[i].imag()+0.5);res[i]=(aa%m+(bb%m<<15)+(cc%m<<30))%m;
    	}return res;
    }
    constexpr int digits(int base)noexcept {return base<=1?0:1+digits(base/10);} constexpr int base=1000000000;
    constexpr int base_digits=digits(base);constexpr int fft_base=10000;constexpr int fft_base_digits=digits(fft_base);
    struct BIG {
    	vector<int>z;int sign;
    	BIG(long long v=0) {*this=v;} 
        BIG&operator=(long long v) {
    		sign=v<0?-1:1;v*=sign;z.clear();for(; v>0; v=v/base)z.push_back((int)(v%base));return*this;} 
        BIG(const string&s) {read(s);} 
    	BIG&operator+=(const BIG&other) {
    		if(sign==other.sign) {for(int i=0,carry=0; i<(int)other.z.size()||carry; ++i) {
    				if(i==(int)z.size())z.push_back(0);z[i]+=carry+(i<(int)other.z.size()?other.z[i]:0);
    				carry=z[i]>=base;if(carry)z[i]-=base;
    		}} else if(other!=0) {*this-=-other;}return*this;} 
        friend BIG operator+(BIG a,const BIG&b) {a+=b;return a;} 
    	BIG&operator-=(const BIG&other) {if(sign==other.sign) 
    		{if((sign==1&&*this>=other)||(sign==-1&&*this<=other)){for(int i=0,carry=0;i<(int)other.z.size()||carry;++i)
    		{z[i]-=carry+(i<(int)other.z.size()?other.z[i]:0);carry=z[i]<0;if(carry)z[i]+=base;}trim();
    		} else {*this=other-*this;this->sign=-this->sign;}} else {*this+=-other;}return*this;} 
        friend BIG operator-(BIG a,const BIG&b) {a-=b;return a;}
    	BIG&operator*=(int v) {if(v<0)sign=-sign,v=-v;
    		for(int i=0,carry=0; i<(int)z.size()||carry; ++i) {
    			if(i==(int)z.size())z.push_back(0);long long cur=(long long)z[i]*v+carry;
    			carry=(int)(cur/base);z[i]=(int)(cur%base);
    		}trim();return*this;} 
        BIG operator*(int v)const {return BIG(*this)*=v;}
    	friend pair<BIG,BIG>divmod(const BIG&a1,const BIG&b1) {
    		int norm=base/(b1.z.back()+1);BIG a=a1.abs()*norm;BIG b=b1.abs()*norm;BIG q,r;q.z.resize(a.z.size());
    		for(int i=(int)a.z.size()-1; i>=0; i--) {
    			r*=base;r+=a.z[i];int s1=b.z.size()<r.z.size()?r.z[b.z.size()]:0;
    			int s2=b.z.size()-1<r.z.size()?r.z[b.z.size()-1]:0;int d=(int)(((long long)s1*base+s2)/b.z.back());
    			r-=b*d;while(r<0)r+=b,--d;q.z[i]=d;
    		}q.sign=a1.sign*b1.sign;r.sign=a1.sign;q.trim();r.trim();return {q,r/norm};} 
        friend BIG sqrt(const BIG&a1) {BIG a=a1;
    		while(a.z.empty()||a.z.size()%2==1)a.z.push_back(0);int n=a.z.size();
    		int firstDigit=(int)::sqrt((double)a.z[n-1]*base+a.z[n-2]);int norm=base/(firstDigit+1);
    		a*=norm;a*=norm;while(a.z.empty()||a.z.size()%2==1)a.z.push_back(0);BIG r=(long long)a.z[n-1]*base+a.z[n-2];
    		firstDigit=(int)::sqrt((double)a.z[n-1]*base+a.z[n-2]);int q=firstDigit;BIG res;
    		for(int j=n/2-1; j>=0; j--) {for(;; --q) {
    			BIG r1=(r-(res*2*base+q)*q)*base*base+(j>0?(long long)a.z[2*j-1]*base+a.z[2*j-2]:0);if(r1>=0){r=r1;break;}
    			}res*=base;res+=q;if(j>0) {
    				int d1=res.z.size()+2<r.z.size()?r.z[res.z.size()+2]:0;
    				int d2=res.z.size()+1<r.z.size()?r.z[res.z.size()+1]:0;
    				int d3=res.z.size()<r.z.size()?r.z[res.z.size()]:0;
    				q=(int)(((long long)d1*base*base+(long long)d2*base+d3)/(firstDigit*2));
    		}}res.trim();return res/norm;} 
        BIG operator/(const BIG&v)const {return divmod(*this,v).first;} 
    	BIG operator%(const BIG&v)const {return divmod(*this,v).second;} 
    	BIG&operator/=(int v) {if(v<0)sign=-sign,v=-v;
    		for(int i=(int)z.size()-1,rem=0; i>=0; --i) {
    			long long cur=z[i]+rem*(long long)base;z[i]=(int)(cur/v);rem=(int)(cur%v);
    		}trim();return*this;} 
        BIG operator/(int v)const {return BIG(*this)/=v;} 
    	int operator%(int v)const {
    		if(v<0)v=-v;int m=0;for(int i=(int)z.size()-1; i>=0; --i)m=(int)((z[i]+m*(long long)base)%v);return m*sign;} 
        BIG&operator*=(const BIG&v) {*this=*this*v;return*this;} 
    	BIG&operator/=(const BIG&v) {*this=*this/v;return*this;} 
    	BIG&operator%=(const BIG&v) {*this=*this%v;return*this;} 
    	bool operator<(const BIG&v)const {if(sign!=v.sign)return sign<v.sign;
    		if(z.size()!=v.z.size())return z.size()*sign<v.z.size()*v.sign;
    		for(int i=(int)z.size()-1; i>=0; i--)if(z[i]!=v.z[i])return z[i]*sign<v.z[i]*sign;return false;} 
        bool operator>(const BIG&v)const {return v<*this;} 
    	bool operator<=(const BIG&v)const {return!(v<*this);} 
    	bool operator>=(const BIG&v)const {return!(*this<v);} 
    	bool operator==(const BIG&v)const {return sign==v.sign&&z==v.z;} 
    	bool operator!=(const BIG&v)const {return!(*this==v);
    	}void trim(){while(!z.empty()&&z.back()==0)z.pop_back();if(z.empty())sign=1;} 
    	bool isZero()const {return z.empty();} 
    	friend BIG operator-(BIG v) {if(!v.z.empty())v.sign=-v.sign;return v;} 
    	BIG abs()const {return sign==1?*this:-*this;} 
    	long long to_num()const {
    		long long res=0;for(int i=(int)z.size()-1; i>=0; i--)res=res*base+z[i];return res*sign;} 
        friend BIG __gcd(const BIG&a,const BIG&b) {return b.isZero()?a:__gcd(b,a%b);} 
    	friend BIG abs(const BIG&a) {return a.abs();}
    	void read(const string&s) {
    		sign=1;z.clear();int pos=0;
    		while(pos<(int)s.size()&&(s[pos]=='-'||s[pos]=='+')) {if(s[pos]=='-')sign=-sign;++pos;}
    		for(int i=(int)s.size()-1; i>=pos; i-=base_digits) {int x=0;
    			for(int j=max(pos,i-base_digits+1); j<=i; j++)x=x*10+s[j]-'0';z.push_back(x);
    		}trim();} 
        friend istream&operator>>(istream&stream,BIG&v) {string s;stream>>s;v.read(s);return stream;} 
    	friend ostream&operator<<(ostream&stream,const BIG&v) {
    		if(v.sign==-1)stream<<'-';stream<<(v.z.empty()?0:v.z.back());
    		for(int i=(int)v.z.size()-2; i>=0; --i)stream<<setw(base_digits)<<setfill('0')<<v.z[i];return stream;
    	} static vector<int>convert_base(const vector<int>&a,int old_digits,int new_digits) {
    		vector<long long>p(max(old_digits,new_digits)+1);p[0]=1;
    		for(int i=1; i<(int)p.size(); i++)p[i]=p[i-1]*10;vector<int>res;long long cur=0;int cur_digits=0;
    		for(int v:a) {cur+=v*p[cur_digits];cur_digits+=old_digits;while(cur_digits>=new_digits)
    			{res.push_back(int(cur%p[new_digits]));cur/=p[new_digits];cur_digits-=new_digits;}
    		}res.push_back((int)cur);while(!res.empty()&&res.back()==0)res.pop_back();return res;} 
        BIG operator*(const BIG&v)const {
    		if(min(z.size(),v.z.size())<150)return mul_simple(v);BIG res;res.sign=sign*v.sign;
    		res.z=multiply_BIG(convert_base(z,base_digits,fft_base_digits),convert_base(v.z,base_digits,fft_base_digits),fft_base);
    		res.z=convert_base(res.z,fft_base_digits,base_digits);res.trim();return res;} 
        BIG mul_simple(const BIG&v)const {BIG res;res.sign=sign*v.sign;res.z.resize(z.size()+v.z.size());
    		for(int i=0; i<(int)z.size(); ++i)if(z[i])for(int j=0,carry=0; j<(int)v.z.size()||carry; ++j) {
    			long long cur=res.z[i+j]+(long long)z[i]*(j<(int)v.z.size()?v.z[j]:0)+carry;
    			carry=(int)(cur/base);res.z[i+j]=(int)(cur%base);
    		}res.trim();return res;
    	}
    };
    mt19937 rng(std::chrono::system_clock::now().time_since_epoch().count());
    BIG BIG_rnd(int n) {string s;for(int i=0; i<n; i++) {s+=uniform_int_distribution<int>('0','9')(rng);}return BIG(s);}
    } using BIG_num::BIG;
    BIG f[100];
    BIG qpow(BIG a,BIG b)
    {
    	BIG ans=1;
    	while(b>0)
    	{
    		if(b%2)
    		{
    			ans=ans*a;
    		}
    		b/=2;
    		a=a*a;
    	}
    	return ans;
    }
    BIG C(BIG n,BIG m)
    {
    	if(n>=m&&n>=0&&m>=0)
    	{
    		BIG up=1,down=1,i;
    		for(i=n-m+1;i<=n;i+=1)
    		{
    			up*=i;
    		}
    		for(i=1;i<=m;i+=1)
    		{
    			down*=i;
    		}
    		return up/down;
    	}
    	else
    	{
    		return 0;
    	}
    }
    int main()
    {
    	ll n,i,j;
    	f[1]=1;
    	for(i=2;i<=50;i+=1)
    	{
    		f[i]=qpow(2,i*(i-1)/2);
    		for(j=1;j<=i-1;j+=1)
    		{
    			f[i]-=f[j]*C(i-1,j-1)*qpow(2,(i-j)*(i-j-1)/2);
    		}
    	}
    	while(cin>>n)
    	{
    		if(n==0)
    		{
    			break;
    		}
    		else
    		{
    			cout<<f[n]<<endl;
    		}
    	}
    	return 0;
    }
    

luogu P7690 [CEOI2002] A decorative fence

  • 方案数统计同 luogu P2467 [SDOI2010] 地精部落 ,但部分写得不太好看的状态转移方程在本题中并不适用,但仍可借鉴其“离散化”思想。

  • 考虑试填。

  • \(f_{i,j,0/1}\) 表示用 \(i\) 块不同的木板构成栅栏,其中最左边的木板的长度从小到大排在第 \(j\) 位(仅是相对大小关系),处于低位/高位的方案数,状态转移方程为 \(\begin{cases} f_{i,j,0}=\sum\limits_{k=j}^{i-1}f_{i-1,k,1} \\ f_{i,j,1}=\sum\limits_{k=1}^{j-1}f_{i-1,k,0} \end {cases}\) ,边界为 \(f_{1,1,0/1}=1\)

  • 特别处理第 \(1\) 块木板的长度和低/高位情况 \(k\) 。接着枚举每位的实际长度 \(j\) 和排名 \(rk\) 使其符合排名即可。

    点击查看代码
    ll f[25][25][2],vis[25];
    int main()
    {
    	ll t,n,c,ans,rk,i,j,k,h;
    	cin>>t;
    	f[1][1][0]=f[1][1][1]=1;
    	for(i=2;i<=20;i++)
    	{
    		for(j=1;j<=i;j++)
    		{
    			for(k=j;k<=i-1;k++)
    			{
    				f[i][j][0]+=f[i-1][k][1];
    			}
    			for(k=1;k<=j-1;k++)
    			{
    				f[i][j][1]+=f[i-1][k][0];
    			}
    		}
    	}
    	for(h=1;h<=t;h++)
    	{
    		cin>>n>>c;
    		k=ans=-1;
    		memset(vis,0,sizeof(vis));
    		for(i=1;i<=n;i++)
    		{
    			if(f[n][i][1]>=c)
    			{
    				ans=i;
    				k=1;
    				break;
    			}
    			else
    			{
    				c-=f[n][i][1]; 
    			}
    			if(f[n][i][0]>=c)
    			{
    				ans=i;
    				k=0;
    				break;
    			}
    			else
    			{
    				c-=f[n][i][0];
    			}
    		}
    		vis[ans]=1;
    		cout<<ans<<" ";
    		for(i=2;i<=n;i++)
    		{
    			k^=1;
    			rk=0;
    			for(j=1;j<=n;j++)
    			{
    				if(vis[j]==0)
    				{
    					rk++;
    					if((k==0&&j<ans)||(k==1&&j>ans))
    					{
    						if(f[n-i+1][rk][k]>=c)
    						{
    							ans=j;
    							break;
    						}
    						else
    						{
    							c-=f[n-i+1][rk][k];
    						}
    					}
    				}
    			}
    			vis[ans]=1;
    			cout<<ans<<" ";
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

luogu P6596 How Many of Them

  • \(e_{i}\) 表示 \(i\) 个节点的无向连通图个数,状态转移方程为 \(e_{i}=2^{\frac{i(i-1)}{2}}-\sum\limits_{j=1}^{i-1}e_{j} \times \dbinom{i-1}{j-1} \times 2^{\frac{(i-j)(i-j-1)}{2}}\) ,边界为 \(e_{1}=1\)

  • \(f_{i,j}\) 表示 \(i\) 个节点构成的,包含 \(j\) 条割边的无向连通图个数,容易有 \(f_{i,0}=e_{i}-\sum\limits_{j=1}^{i-1}f_{i,j}\) 。接着考虑如何求 \(f_{i,j}(j>0)\)

  • \(g_{i,j,k}\) 表示 \(i\) 个节点构成的,包含 \(j\) 个连通块和 \(k\) 条割边的无向图产生的贡献(注意不是个数)。

  • 计算 \(f_{i,j}\) 时,枚举与钦定标号为 \(1\) 的点所在的边双连通分量的大小 \(k \in [1,i-1]\) ,其方案数为 \(\dbinom{i-1}{k-1} \times f_{k,0}\) 。接着枚举删掉 \(1\) 所在的边双连通分量后图中剩余部分的连通块的个数 \(h \in [1,\min(i-k,j)]\) ,其方案数为 \(g_{i-k,h,j-h}\) ;同时,这 \(h\) 个连通块均要向 \(1\) 所在的边双连通分量连一条割边,方案数为 \(k^{h}\) 。最终,有状态转移方程为 \(f_{i,j}= \begin{cases} \sum\limits_{k=1}^{i-1}(\dbinom{i-1}{k-1} \times f_{k,0} \times (\sum\limits_{h=1}^{\min(i-k,j)} g_{i-k,h,j-h} \times k^{h})) & j \ne 0 \\ e_{i}-\sum\limits_{k=1}^{i-1}f_{i,k} & j=0 \end{cases}\)

  • 问题又转化为如果求 \(g_{i,j,k}\) 。类似 \(f\) 的求法,枚举此时编号最小的节点所在的连通块的大小 \(h \in [1,i]\) 和内部割边数量 \(l \in [0,\min(h-1,k)]\) ,其方案数为 \(f_{h,l} \times \dbinom{i-1}{h-1}\) ;将这 \(h\) 个点中选出一个来和 \(1\) 所在的边双连通分量相连,再加上剩余部分的贡献,其方案数为 \(h \times g_{i-h,j-1,k-l}\) 。最终,有状态转移方程为 \(g_{i,j,k}=\sum\limits_{h=1}^{i}(\sum\limits_{l=0}^{\min(h-1,k)}f_{h,l} \times \dbinom{i-1}{h-1} \times h \times g_{i-h,j-1,k-l})\) ,边界为 \(g_{0,0,0}=1\)

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

    点击查看代码
    ll inv[60],jc[60],jc_inv[60],e[60],f[60][60],g[60][60][60];
    const ll p=1000000007;
    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;
    }
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0;
    }
    int main()
    {
    	ll n,m,ans=0,sum,i,j,k,h,l;
    	cin>>n>>m;
    	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;
    	}
    	e[1]=1;
    	for(i=2;i<=n;i++)
    	{
    		e[i]=qpow(2,i*(i-1)/2,p);
    		for(j=1;j<=i-1;j++)
    		{
    			e[i]=(e[i]-(e[j]*C(i-1,j-1,p)%p)*qpow(2,(i-j)*(i-j-1)/2,p)%p+p)%p;
    		}
    	}
    	g[0][0][0]=1;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=i-1;j++)
    		{
    			for(k=1;k<=i-1;k++)
    			{
    				sum=0;
    				for(h=1;h<=min(i-k,j);h++)
    				{
    					sum=(sum+qpow(k,h,p)*g[i-k][h][j-h]%p)%p;
    				}
    				f[i][j]=(f[i][j]+(C(i-1,k-1,p)*f[k][0]%p)*sum%p)%p;
    			}
    		}
    		f[i][0]=e[i];
    		for(j=1;j<=i-1;j++)
    		{
    			f[i][0]=(f[i][0]-f[i][j]+p)%p;
    		}
    		for(j=1;j<=i;j++)
    		{
    			for(k=0;k<=i-1;k++)
    			{
    				for(h=1;h<=i;h++)
    				{
    					sum=0;
    					for(l=0;l<=min(h-1,k);l++)
    					{
    						sum=(sum+((f[h][l]*C(i-1,h-1,p)%p)*h%p)*g[i-h][j-1][k-l])%p;
    					}	
    					g[i][j][k]=(g[i][j][k]+sum)%p;
    				}
    			}
    		}
    	}
    	for(i=0;i<=min(n-1,m);i++)
    	{
    		ans=(ans+f[n][i])%p;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P6064 [USACO05JAN] Naptime G

  • 多倍经验: SP283 NAPTIME - Naptime

  • 定义熟睡指睡觉但不入睡。

  • 将问题拆成两部分:第 \(1\) 个小时不熟睡的答案和第 \(1\) 个小时熟睡(第 \(0\) 个小时入睡)的答案。

  • \(f_{i,j,0/1}\) 表示前 \(i\) 个小时休息了 \(j\) 个小时,且第 \(i\) 个小时不在/在休息的恢复体力的最大值,转移方程为 \(\begin{cases} f_{i,j,0}=\max(f_{i-1,j,0},f_{i-1,j,1}) \\ f_{i,j,1}=\max(f_{i-1,j-1,0},f_{i-1,j-1,1}+u_{i}) \end{cases}\) ,两部分的边界分别为 \(\begin{cases} f_{1,0,0}=0 \\ f_{1,1,1}=0 \end{cases}\)\(\begin{cases} f'_{1,0,0}=0 \\ f'_{1,1,1}=u_{1} \\ \end{cases}\)

  • 最终,有 \(\max(f_{n,b,0},f_{n,b,1},f'_{n,b,1})\) 即为所求。

    点击查看代码
    ll u[4000],f[2][4000][2];
    int main()
    {
    	ll n,b,ans=0,i,j;
    	cin>>n>>b;
    	for(i=1;i<=n;i++)
    	{
    		cin>>u[i];
    	}
    	memset(f,-0x3f,sizeof(f));
    	f[1][0][0]=f[1][1][1]=0;
    	for(i=2;i<=n;i++)
    	{
    		for(j=0;j<=i;j++)
    		{
    			f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
    			if(j-1>=0)
    			{
    				f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+u[i]);
    			}
    		}
    	}
    	ans=max(f[n&1][b][0],f[n&1][b][1]);
    	memset(f,-0x3f,sizeof(f));
    	f[1][0][0]=0;
    	f[1][1][1]=u[1];
    	for(i=2;i<=n;i++)
    	{
    		for(j=0;j<=i;j++)
    		{
    			f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
    			if(j-1>=0)
    			{
    				f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+u[i]);
    			}
    		}
    	}
    	ans=max(ans,f[n&1][b][1]);
    	cout<<ans<<endl;
    	return 0;
    }
    

Acwing 289. 环路运输

  • 破环为链,令 \(a_{i+n}=a_{i}(i \in [1,n])\)

  • 对于原环形公路上的两座仓库 \(i,j(1 \le j<i \le n)\) ,其运送货物的代价为 \(a_{i}+a_{j}+\begin{cases} i-j & i-j \le \frac{n}{2} \\ j+n-i & i-j>\frac{n}{2} \end{cases}\) ,进一步归纳为对于两座仓库 \(i,j(1 \le j<i \le 2n,i-j \le \frac{n}{2})\) 运送货物的代价为 \(a_{i}+a_{j}+i-j\)

  • 单调队列维护递增的 \(i\)\(a_{i}-i\) 即可。

    点击查看代码
    int a[2000010];
    deque<int>q;
    int main()
    {
    	int n,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		a[i+n]=a[i];
    	}
    	for(i=1;i<=2*n;i++)
    	{
    		while(q.empty()==0&&i-q.front()>n/2)
    		{
    			q.pop_front();
    		}
    		if(q.empty()==0)
    		{
    			ans=max(ans,a[i]+a[q.front()]+i-q.front());
    		}
    		while(q.empty()==0&&a[q.back()]-q.back()<=a[i]-i)
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P3750 [六省联考 2017] 分手是祝愿

  • 大力结论

    • \(x\) 的约数集合无法由其他数的约数集合异或所得。
      • 反证法即可。
    • 从大到小枚举每盏灯,如果是 \(1\) 则进行一次操作。
      • 每盏灯不会被主动操作两次或两次以上。
      • 对第 \(i \in [1,n)\) 盏灯的操作不会影响 \(j(j \in (i,n])\) 盏灯的贡献。
  • \(f_{i}\) 表示从需要操作 \(i\) 次才能全灭到需要操作 \(i-1\) 次才能全灭的期望步数,有 \(f_{i}=\dfrac{i}{n}+\dfrac{n-i}{n} \times (f_{i+1}+f_{i}+1)\) ,移项得到状态转移方程为 \(f_{i}=\begin{cases} \dfrac{(n-i)f_{i+1}+n}{i} & i>k \\ 1 & i \le k \end{cases}\) ,边界为 \(f_{n}=1\)

  • 暴力得到在最优策略下需要操作的开关数量 \(cnt\)

  • 当通过操作小于等于 \(k\) 个开关使所有灯都灭掉时按照最优策略一定会操作 \(\min(cnt,k)\) 个开关。

  • 否则,有 \(n! \times (\sum\limits_{i=1}^{cnt}f_{i})\) 即为所求。

    点击查看代码
    const ll p=100003;
    ll a[100010],f[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,k,ans=0,cnt=0,i,j;
    	cin>>n>>k;    
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	f[n]=1;
    	for(i=n-1;i>=k+1;i--)
    	{
    		f[i]=(((n-i)*f[i+1]%p+n)%p)*qpow(i,p-2,p)%p;
    	}
    	for(i=1;i<=k;i++)
    	{
    		f[i]=1;
    	}
    	for(i=n;i>=1;i--)
    	{
    		if(a[i]==1)
    		{
    			cnt++;
    			for(j=1;j*j<=i;j++)
    			{
    				if(i%j==0)
    				{
    					a[j]^=1;
    					if(j*j!=i)
    					{
    						a[i/j]^=1;
    					}
    				}
    			}
    		}
    	}
    	for(i=1;i<=cnt;i++)
    	{
    		ans=(ans+f[i])%p;
    	}
    	for(i=1;i<=n;i++)
    	{
    		ans=ans*i%p;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

SP15620 POSTERIN - Postering

  • 多倍经验: luogu P3467 [POI2008] PLA-Postering

    点击查看代码
    stack<int>s;
    int main()
    {
    	int n,i,x,y,ans=0;
    	cin>>n;
    	s.push(0);
    	for(i=1;i<=n;i++)
    	{
    		cin>>x>>y;
    		while(s.empty()==0&&y<=s.top())
    		{
    			if(s.top()==y)
    			{
    				ans++;
    			}
    			s.pop();
    		}
    		s.push(y);
    	}
    	cout<<n-ans;
    	return 0;
    }
    

7.5

闲话

  • 早起来机房后,“机房施工队”的 @lty_ylzsx@wkh2008 将指甲刀作为扳手,强行将限位器和纱窗又给拆了。
  • \(miaomiao\) 放了环形与后效性处理 \(DP\) 的视频。
  • 学校 \(OJ\) 刷出了一条正经又不太正经的一言,好奇 \(miaomiao\) 从哪里找的。
  • 午休 \(huge\) 查宿。宿舍多人看书被宿管抓了,但因为 @xrlong 靠门近被 \(D\) 了;
  • 下午空调冷凝管换了个位置滴水, \(miaomiao\) 再次保修; \(miaomiao\) 给发了宿舍的空调遥控器。空调停止制冷后,显示 \(83℃\)\(feifei\) 进行保修,无意间透露了“机房施工队”会拆纱窗和限位器。
  • 吃完晚饭,空调突然就好了,过了一会儿又坏了, \(feifei\) 称“同学们,这个空调怕吵,只要大家安静点空调就好了”。

做题纪要

luogu P3706 [SDOI2017] 硬币游戏

  • 观察到字符串在拼接过程中会因前后缀导致出现“截胡”。即若 \(s_{j}\) 存在长度为 \(len\) 的前缀被作为 \(s_{i}\) 的后缀,则会额外产生 \(\dfrac{1}{2^{m-len}}\) 的贡献。

  • \(f_{i}\) 表示第 \(i\) 个同学胜利的概率, \(P\) 表示任意长度的一个串不包含任何一个同学猜的序列的概率,有 \(\begin{cases}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{m}[s_{i,1 \sim k}=s_{j,m-k+1 \sim m}] \times f_{j} \times \frac{1}{2^{m-k}}=\frac{1}{2^{m}} \times P & i \in [1,n] \\ \sum\limits_{i=1}^{n}f_{i}=1 \end{cases}\) ,移项。

  • 高斯消元解一下,注意 \(\dfrac{1}{2^{300}}\)python3 算了下约等于 \(4.909093465297727 \times 10^{-91}\) 。注意精度。

    点击查看代码
    const ull base=13331;
    const double eps=1e-10;
    ull a[310][310],jc[310];
    double mi[310],g[310][310];
    char s[310][310];
    void sx_hash(char s[],ull a[],ull len)
    {
    	for(ull i=0;i<=len;i++)
    	{
    		a[i]=(i==0)?0:a[i-1]*base+s[i];
    	}
    }
    ull ask_hash(ull a[],ull l,ull r)
    {
    	return a[r]-a[l-1]*jc[r-l+1];
    }
    void Gauss_Jordan(ull n)
    {
    	for(ull i=1;i<=n;i++)
    	{
    		ull val=i;
    		for(ull j=i;j<=n;j++)
    		{
    			if(fabs(g[j][i])-fabs(g[val][i])>eps)
    			{
    				val=j;
    			}
    		}
    		for(ull j=1;j<=n+1;j++)
    		{
    			swap(g[i][j],g[val][j]);
    		}
    		if(g[i][i]!=0)
    		{
    			for(ull j=1;j<=n;j++)
    			{
    				if(j!=i)
    				{
    					for(ull k=i+1;k<=n+1;k++)
    					{
    						g[j][k]-=g[i][k]*g[j][i]/g[i][i];
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ull n,m,i,j,k;
    	cin>>n>>m;
    	for(i=0;i<=m;i++)
    	{
    		jc[i]=(i==0)?1:jc[i-1]*base;
    		mi[i]=(i==0)?1:mi[i-1]/2;
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s[i]+1);
    		sx_hash(s[i],a[i],m);
    	}
    	for(i=1;i<=n;i++)
    	{
    		g[i][i]=1;
    		g[i][n+1]=-mi[m];
    		for(j=1;j<=n;j++)
    		{
    			for(k=1;k<=m;k++)
    			{
    				if(ask_hash(a[i],1,k)==ask_hash(a[j],m-k+1,m))
    				{
    					g[i][j]+=mi[m-k];
    				}
    			}
    		}
    		g[n+1][i]=1;
    	}
    	g[n+1][n+2]=1;
    	Gauss_Jordan(n+1);
    	for(i=1;i<=n;i++)
    	{
    		printf("%.10lf\n",g[i][n+2]/g[i][i]);
    	}
    	return 0;
    }
    

luogu P6125 [JSOI2009] 有趣的游戏

  • luogu P3706 [SDOI2017] 硬币游戏 不同的是每个字母的出现概率不同。

  • 但方程基本不变,有 \(\begin{cases}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{l}[s_{i,1 \sim k}=s_{j,l-k+1 \sim l}] \times f_{j} \times \prod\limits_{h=k+1}^{l}\frac{p_{s_{i,h}}}{q_{s_{i,h}}}=\prod\limits_{h=1}^{l}\frac{p_{s_{i,h}}}{q_{s_{i,h}}} \times P & i \in [1,n] \\ \sum\limits_{i=1}^{n}f_{i}=1 \end{cases}\) ,移项。

  • 注意精度。

    点击查看代码
    const ull base=13331;
    const double eps=1e-10;
    ull a[310][310],jc[310];
    double p[310],sum[310][310],g[310][310];
    char s[310][310];
    ull val(char x)
    {
    	return x-'A'+1;
    }
    void sx_hash(char s[],ull a[],ull len)
    {
    	for(ull i=0;i<=len;i++)
    	{
    		a[i]=(i==0)?0:a[i-1]*base+s[i];
    	}
    }
    ull ask_hash(ull a[],ull l,ull r)
    {
    	return a[r]-a[l-1]*jc[r-l+1];
    }
    void Gauss_Jordan(ull n)
    {
    	for(ull i=1;i<=n;i++)
    	{
    		ull val=i;
    		for(ull j=i;j<=n;j++)
    		{
    			if(fabs(g[j][i])-fabs(g[val][i])>eps)
    			{
    				val=j;
    			}
    		}
    		for(ull j=1;j<=n+1;j++)
    		{
    			swap(g[i][j],g[val][j]);
    		}
    		if(g[i][i]!=0)
    		{
    			for(ull j=1;j<=n;j++)
    			{
    				if(j!=i)
    				{
    					for(ull k=i+1;k<=n+1;k++)
    					{
    						g[j][k]-=g[i][k]*g[j][i]/g[i][i];
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ull n,l,m,q,i,j,k;
    	cin>>n>>l>>m;
    	for(i=0;i<=l;i++)
    	{
    		jc[i]=(i==0)?1:jc[i-1]*base;
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>p[i]>>q;
    		p[i]/=1.0*q;    
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>(s[i]+1);
    		sx_hash(s[i],a[i],l);
    		sum[i][0]=1;
    		for(j=1;j<=l;j++)
    		{
    			sum[i][j]=sum[i][j-1]*p[val(s[i][j])];
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		if(sum[i][l]==0)
    		{
    			g[i][i]=1;
    		}
    		else
    		{
    			g[i][n+1]=-1;
    			for(j=1;j<=n;j++)
    			{
    				for(k=1;k<=l;k++)
    				{
    					if(ask_hash(a[i],1,k)==ask_hash(a[j],l-k+1,l))
    					{
    						g[i][j]+=1.0/sum[i][k];
    					}
    				}
    			}
    			g[n+1][i]=1;
    		}
    	}
    	g[n+1][n+2]=1;
    	Gauss_Jordan(n+1);
    	for(i=1;i<=n;i++)
    	{
    		printf("%.2lf\n",fabs(g[i][n+2]/g[i][i])<eps?0:g[i][n+2]/g[i][i]);
    	}
    	return 0;
    }
    

CF1036C Classy Numbers

  • 多倍经验: [ABC154E] Almost Everywhere Zero

  • 记忆化搜索。

    点击查看代码
    ll a[25],f[25][25];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll pre,ll limit)
    {
    	if(pos<=0)
    	{
    		return (pre<=3);
    	}
    	if(pre>3)
    	{
    		return 0;
    	}
    	if(f[pos][pre]!=-1&&limit==0)
    	{
    		return f[pos][pre];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		ans+=dfs(pos-1,pre+(i!=0),(i==maxx)*limit);
    	}
    	return (limit==0)?f[pos][pre]=ans:ans;
    }
    ll ask(ll n)
    {
    	ll len=divide(n,a);
    	return dfs(len,0,1);
    }
    int main()
    {
    	ll t,l,r,i;
    	cin>>t;
    	memset(f,-1,sizeof(f));
    	for(i=1;i<=t;i++)
    	{
    		cin>>l>>r;
    		cout<<ask(r)-ask(l-1)<<endl;
    	}
    	return 0;
    }
    

CF24D Broken robot

  • 高斯消元解带状矩阵板子。

  • \(f_{i,j}\) 表示机器人从位置 \((i,j)\) 走到最后一行,所需行动次数的期望步数。状态转移方程为 \(\begin{cases} f_{i,1}=\frac{1}{2} \times (f_{i,1}+f_{i+1,1})+1 & m=1,j=1 \\ f_{i,j}=\frac{1}{3} \times (f_{i,j}+f_{i,j+1}+f_{i+1,j})+1 & m \ne 1,j=1 \\ f_{i,j}=\frac{1}{4} \times (f_{i,j}+f_{i,j-1}+f_{i,j+1}+f_{i+1,j})+1 & m \ne 1,2<j<m \\ f_{i,j}=\frac{1}{3} \times (f_{i,j}+f_{i,j-1}+f_{i,j+1})+1 & m \ne 1,j=m \end{cases}\) ,边界为 \(f_{n,j}=0\)

  • \(m=1\) 时,特殊处理,移项有 \(f_{i,1}=f_{i+1,1}+2\) ,得到 \(f_{x,y}=2(n-1-x+1)=2(n-x)\)

  • \(m \ne 1\) 时,发现 \(f_{i,j}\)\(f_{i+1,j'}\) 没有影响,故可以自下而上进行高斯消元,使得计算 \(f_{i,j}\) 时将 \(f_{i+1,j'}\) 作为已知量。 接着, \(f_{i,j}\) 仅会影响 \(f_{i,j-1},f_{i,j},f_{i,j+1}\) (如果存在的话),这样的矩阵很稀疏,且呈带状,高斯消元解一下即可。

  • 最终,有 \(f_{x,y}\) 即为所求。

    点击查看代码
    const double eps=1e-10;
    double f[1010][1010],g[1010][1010];
    void Gauss_band_matrix(int n,int band,double ans[])
    {
    	for(int i=1;i<=n;i++)
    	{
    		if(fabs(g[i][i])<eps)
    		{
    			for(int j=i+1;j<=min(n,i+band);j++)
    			{
    				if(fabs(g[j][i])>=eps)
    				{
    					for(int k=i;k<=min(n,i+2*band);k++)
    					{
    						swap(g[i][k],g[j][k]);
    					}
    					break;
    				}
    			}
    		}
    		if(fabs(g[i][i])>=eps)
    		{
    			for(int j=i+1;j<=min(n,i+band);j++)
    			{
    				for(int k=i+1;k<=min(n,i+2*band);k++)
    				{
    					g[j][k]-=g[i][k]*g[j][i]/g[i][i];
    				}
    				g[j][n+1]-=g[i][n+1]*g[j][i]/g[i][i];
    				g[j][i]=0;//可以删去,因为后续 g[j][i] 不会再用到
    			}
    		}
    	}
    	for(int i=n;i>=1;i--)
    	{
    		ans[i]=g[i][n+1];
    		for(int j=i+1;j<=min(n,i+2*band);j++)
    		{
    			ans[i]-=g[i][j]*ans[j];
    		}
    		ans[i]/=g[i][i];
    	}
    }
    int main()
    {
    	int n,m,x,y,i,j;
    	cin>>n>>m>>x>>y;
    	if(m==1)
    	{
    		f[x][y]=2*(n-x);
    	}
    	else
    	{
    		for(i=n-1;i>=x;i--)
    		{
    			g[1][1]=2.0;
    			g[1][2]=-1.0;
    			g[1][m+1]=1.0*f[i+1][1]+3;
    			for(j=2;j<=m-1;j++)
    			{
    				g[j][j]=3.0;
    				g[j][j-1]=-1.0;
    				g[j][j+1]=-1.0;
    				g[j][m+1]=1.0*f[i+1][j]+4;
    			}
    			g[m][m]=2.0;
    			g[m][m-1]=-1.0;
    			g[m][m+1]=1.0*f[i+1][m]+3;
    			Gauss_band_matrix(m,1,f[i]);
    		}
    	}
    	printf("%.4lf\n",f[x][y]);
    	return 0;
    }
    

牛客 NC274961 小红的最大价值

  • 由于有绝对值的存在, \(i<j\) 的限制不再有用,说明可以随便选择数对。

  • \(\max\limits_{i=1}^{n} \{ a_{i} \}-\min\limits_{i=1}^{n} \{ a_{i} \}>k\) ,选择 \(\max\limits_{i=1}^{n} \{ a_{i} \},\min\limits_{i=1}^{n} \{ a_{i} \}\) ,有 \(\max\limits_{i=1}^{n} \{ a_{i} \}\) 即为所求;否则选择 \(\max\limits_{i=1}^{n} \{ a_{i} \}\) 和第二大的数,有第二大的数即为所求。

    点击查看代码
    ll a[100010];
    int main()
    {
    	ll n,k,maxx=0,minn=0x7f7f7f7f,ans=0,i;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	sort(a+1,a+1+n);
    	cout<<((abs(a[n]-a[1])>k)?a[n]:a[n-1])<<endl;
    	return 0;
    }
    

牛客 NC274963 小红的约数

  • \(f_{d}(n)\) 显然为积性函数。考虑算出所有的 \(f_{d}(p_{i}^{a_{i}})\) ,接着乘起来。

  • \(\begin{aligned} f_{d}(p_{i}^{a_{i}})=\sum\limits_{j=0}^{a_{i}}p_{i}^{jd}=\sum\limits_{j=0}^{a_{i}}(p_{i}^{d})^{j} \end{aligned}\) ,看作首项为 \(1\) 公比为 \(p_{i}^{d}\) 的等比数列 ,特判公比为 \(1\) 即可。

    点击查看代码
    const ll p=1000000007;
    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 w,d,ans=1,pp,a,i;
    	cin>>w>>d;
    	for(i=1;i<=w;i++)
    	{
    		cin>>pp>>a;
    		pp=qpow(pp,d,p);
    		if(pp==1)
    		{
    			ans=(ans*((pp*a%p+1)%p))%p;
    		}
    		else
    		{
    			ans=(ans*(((pp*(qpow(pp,a,p)-1)%p)*qpow(pp-1,p-2,p)%p)+1)%p)%p;
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

7.6

闲话

  • 上午 \(miaomiao\) 放了单调队列优化 \(DP\) 的视频;有不少小孩哥来上公益课,去吃饭的路上还听见他讨论交服务器的钱。
  • 中午因为要打 [SNCPC2024] 2024 年陕西省大学生程序设计竞赛重现赛 ,找 \(feifei\) 申请能不能中午不回宿舍, \(miaomiao\) 同意了。吃完午饭回了趟宿舍拿了桶泡面来机房吃了。
  • 下午小孩哥接着上公益课,还听见其中有在机房打乒乓球的,离谱。
  • 晚上想打 \(ABC\) ,但 \(miaomiao\) 说专题没写完的不让去打,想了想说写了 \(6/9\) 题才让打,又降低标准说写 \(5/9\) 题才让打;顺便 \(D\) 了下 @STA_Morlin 没写单调队列优化 \(DP\) 的题,颇有“威胁”意义地问他是不是不想跟着团队一起往下干了。不算我以前写过的,正好 \(6\) 道题。
  • 晚休是 \(miaomiao\) 和数奥教练查宿;和家长打电话说了下报志愿的事情,因为时间过长和过晚,被数奥教练 \(D\) 了。

做题纪要

牛客 NC275023 小红的图上划分

  • 将顺序删边的过程看作逆序加边。要使划分价值最大,考虑从大到小加边,这样的话连通块数量是单调不升的,划分价值是单调不升的。最终,设 \(r\) 个连通块一定是最优答案。

  • \(f_{i}\) 表示存在 \(i\) 个连通块时最大的划分价值(只有第一条边对答案有贡献)。

    点击查看代码
    struct node
    {
    	ll u,v,w;
    }e[200010];
    ll fa[200010],f[200010];
    bool cmp(node a,node b)
    {
    	return a.w>b.w;
    }
    ll dsu_find(ll x)
    {
    	return (fa[x]==x)?x:fa[x]=dsu_find(fa[x]);
    }
    void dsu_merge(ll x,ll y,ll &cnt)
    {
    	x=dsu_find(x);
    	y=dsu_find(y);
    	if(x!=y)
    	{
    		fa[x]=y;
    		cnt--;    
    	}
    }
    int main()
    {
    	ll n,m,q,l,r,cnt,i;
    	cin>>n>>m>>q;
    	cnt=n;
    	memset(f,-0x3f,sizeof(f));
    	for(i=1;i<=n;i++)
    	{
    		fa[i]=i;
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>e[i].u>>e[i].v>>e[i].w;
    	}
    	sort(e+1,e+1+m,cmp);
    	for(i=1;i<=m;i++)
    	{
    		dsu_merge(e[i].u,e[i].v,cnt);
    		f[cnt]=max(f[cnt],e[i].w);
    	}
    	for(i=1;i<=q;i++)
    	{
    		cin>>l>>r;
    		if(f[r]==-4485090715960753727)
    		{
    			cout<<"NO ANSWER"<<endl;
    		}
    		else
    		{
    			cout<<f[r]<<endl;
    		}
    	}
    	return 0;
    }
    

luogu P1714 切蛋糕

  • 多倍经验: LibreOJ 10176. 「一本通 5.5 例 2」最大连续和

  • \(sum_{i}=\sum\limits_{j=1}^{i}p_{j}\) ,等价于求 \(\max\limits_{1 \le l \le r \le n \land r-l+1 \le m} \{ sum_{r}-sum_{l-1} \}=\max\limits_{0 \le l<r \le n \land r-l \le m} \{ sum_{r}-sum_{l} \}=\max\limits_{r=1}^{n} \{ sum_{r}- \min\limits_{l=\max(0,r-m)}^{r} \{ sum_{l} \} \}\)

  • 单调队列维护 \(sum_{i}\) 即可。

    点击查看代码
    ll a[500010],sum[5000010];
    deque<ll>q;
    int main()
    {
    	ll n,m,ans=-0x7f7f7f7f,i;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		sum[i]=sum[i-1]+a[i];
    	}
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.empty()==0&&i-q.front()>m)
    		{
    			q.pop_front();
    		}
    		if(q.empty()==0)
    		{
    			ans=max(ans,sum[i]-sum[q.front()]);
    		}
    		while(q.empty()==0&&sum[q.back()]>sum[i])
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P2627 [USACO11OPEN] Mowing the Lawn G

  • 多倍经验: luogu P2034 选择数字

  • \(f_{i,0/1}\) 表示到对于前 \(i\) 头奶牛,不安排/安排第 \(i\) 头奶牛的最大效率。状态转移方程为 \(\begin{cases} f_{i,0}=\max(f_{i-1,0},f_{i-1,1}) \\ f_{i,1}=\max\limits_{j=\max(1,i-k+1)}^{i} \{ f_{j-1,0}+sum_{i}-sum_{j-1} \} \end{cases}\) ,边界为 \(f_{0,0}=f_{0,1}=0\)

  • 单调队列维护 \(f_{i,0}-sum_{i}\) 即可。

    点击查看代码
    ll a[100010],sum[100010],f[100010][2];
    deque<ll>q;
    int main()
    { 
    	ll n,k,i;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		sum[i]=sum[i-1]+a[i];
    	}
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		f[i][0]=max(f[i-1][0],f[i-1][1]);
    		while(q.empty()==0&&i-q.front()>k)
    		{
    			q.pop_front();
    		}
    		if(q.empty()==0)
    		{
    			f[i][1]=sum[i]+f[q.front()][0]-sum[q.front()];
    		}
    		while(q.empty()==0&&f[q.back()][0]-sum[q.back()]<f[i][0]-sum[i])
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<max(f[n][0],f[n][1])<<endl;
    	return 0;
    }
    

luogu P10696 [SNCPC2024] 写都写了,交一发吧

  • 自己按位与自己一定是最优情况,有 \(\max\limits_{i=1}^{n} \{ g_{i} \}\) 即为所求。

    点击查看代码
    int main()
    {
    	ll t,n,g,maxx=0,i,j;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		maxx=0;
    		for(j=1;j<=n;j++)
    		{
    			cin>>g;
    			maxx=max(maxx,g);
    		}
    		cout<<maxx<<endl;
    	}
    	return 0;
    }
    

luogu P10691 [SNCPC2024] chmod

  • 选择结构。

    点击查看代码
    char s[5];
    int main()
    {
    	ll t,j,i,k;
    	cin>>t;
    	for(k=1;k<=t;k++)
    	{
    		cin>>(s+1);
    		for(i=1;i<=3;i++)
    		{
    			if(s[i]=='0')
    			{
    				cout<<"---";
    			}
    			if(s[i]=='1')
    			{
    				cout<<"--x";
    			}
    			if(s[i]=='2')
    			{
    				cout<<"-w-";
    			}
    			if(s[i]=='3')
    			{
    				cout<<"-wx";
    			}
    			if(s[i]=='4')
    			{
    				cout<<"r--";
    			}
    			if(s[i]=='5')
    			{
    				cout<<"r-x";
    			}
    			if(s[i]=='6')
    			{
    				cout<<"rw-";
    			}
    			if(s[i]=='7')
    			{
    				cout<<"rwx";
    			}
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

luogu P10697 [SNCPC2024] 消失的数字

  • 等价于 \(0 \sim n\) 中有多少个整数在十进制表示下不包含 \(x\) ,记忆化搜索即可。

    点击查看代码
    ll a[50],f[50][10];
    ll divide(ll n,ll a[])
    {
    	ll len=0;
    	while(n)
    	{
    		len++;
    		a[len]=n%10;
    		n/=10;
    	}
    	return len;
    }
    ll dfs(ll pos,ll limit,ll x)
    {
    	if(pos<=0)
    	{
    		return 1;
    	}
    	if(f[pos][x]!=-1&&limit==0)
    	{
    		return f[pos][x];
    	}
    	ll ans=0,maxx=(limit==0)?9:a[pos],i;
    	for(i=0;i<=maxx;i++)
    	{
    		if(i!=x)
    		{
    			ans+=dfs(pos-1,(i==maxx)*limit,x);
    		}
    	}
    	return (limit==0)?f[pos][x]=ans:ans;
    }
    ll ask(ll n,ll x)
    {
    	ll len=divide(n,a);
    	return dfs(len,1,x);
    }
    int main()
    {
    	ll t,n,x,i;
    	cin>>t;
    	memset(f,-1,sizeof(f));
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>x;
    		cout<<ask(n,x)<<endl;
    	}
    	return 0;
    }
    

luogu P2564 [SCOI2009] 生日礼物

  • 观察到 \(k\) 较小,每次可以暴力检验每种颜色是否出现过。

  • 对位置进行排序后双指针,双端队列实现左右指针即可,注意仅枚举彩珠出现的位置,而不是整条彩带。

    点击查看代码
    struct node
    {
    	ll pos,col;
    }e[1000010];
    ll vis[1000010];
    deque<pair<ll,ll> >q;
    bool cmp(node a,node b)
    {
    	return a.pos<b.pos;
    }
    int main()
    { 
    	ll n,m=0,k,t,ans=0x7f7f7f7f,flag,i,j;
    	cin>>n>>k;
    	for(i=1;i<=k;i++)
    	{
    		cin>>t;
    		for(j=1;j<=t;j++)
    		{
    			m++;
    			cin>>e[m].pos;
    			e[m].col=i;
    		}
    	}
    	sort(e+1,e+1+m,cmp);
    	for(i=1;i<=m;i++)
    	{
    		flag=0;
    		vis[e[i].col]++;
    		q.push_back(make_pair(e[i].pos,e[i].col));
    		while(q.empty()==0&&vis[q.front().second]>=2)
    		{
    			vis[q.front().second]--;
    			q.pop_front();
    		}
    		for(j=1;j<=k;j++)
    		{
    			flag|=(vis[j]==0);
    		}
    		if(q.empty()==0&&flag==0)
    		{
    			ans=min(ans,q.back().first-q.front().first);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P10702 [SNCPC2024] 下棋

  • \(k\) 进制下所有数位的乘积为自身因子等价于 \(k\) 进制下所有数位为自身因子。

  • \(1 \sim k-1\) 均是 \(k\) 进制下的 LNC 数,且 \(k\) 进制下的 LNC 数在 \(k\) 进制表示下末位一定不为 \(0\)

  • \(x\)\(k\) 进制表示下末尾为 \(0\) ,先手怎么取末位都非 \(0\) ,而后手却可以仍让末位变成 \(0\) ,使状态延续,最后后手必胜。故若 \(x\)\(k\) 进制表示下末尾不为 \(0\) ,则先手必胜。

  • 最终,有使得 \(k \nmid x\) 且最小的 \(k\) 即为所求。

    点击查看代码
    int main()
    {  
    	ll t,x,i,j;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>x;
    		for(j=2;j<=1000000;j++)
    		{
    			if(x%j!=0)
    			{
    				cout<<j<<endl;
    				break;
    			}
    		}
    	}
    	return 0;
    }
    

POJ1821 Fence

  • 将木匠按照 \(s\) 升序排序。

  • \(f_{i,j}\) 表示前 \(i\) 个木匠处理到第 \(j\) 块木板时的最大报酬,状态转移方程为 \(f_{i,j}=\max\limits(f_{i-1,j},f_{i,j-1},[j \ge s_{i}] \times \max\limits_{k=j-l_{i}+1}^{s_{i}} \{ p_{i}(j-k+1)+f_{i-1,k-1} \})=\max\limits(f_{i-1,j},f_{i,j-1},[j \ge s_{i}] \times (p_{i}j+\max\limits_{k=j-l_{i}}^{s_{i}-1} \{ f_{i-1,k}-p_{i}k \}))\)

  • 单调队列维护 \(f_{i-1,k}-p_{i}k\) 即可。

    点击查看代码
    struct node
    {
    	int l,p,s;
    }a[110];
    int f[110][16010];
    deque<int>q;
    bool cmp(node a,node b)
    {
    	return a.s<b.s;
    }
    int main()
    {
    	int n,m,i,j,k;
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>a[i].l>>a[i].p>>a[i].s;
    	}
    	sort(a+1,a+1+m,cmp);
    	for(i=1;i<=m;i++)
    	{
    		q.clear();
    		for(k=max(0,a[i].s-a[i].l);k<=a[i].s-1;k++)
    		{
    			while(q.empty()==0&&f[i-1][q.back()]-a[i].p*q.back()<=f[i-1][k]-a[i].p*k)
    			{
    				q.pop_back();
    			}
    			q.push_back(k);
    		}
    		for(j=1;j<=n;j++)
    		{
    			f[i][j]=max(f[i-1][j],f[i][j-1]);
    			if(j>=a[i].s)
    			{
    				while(q.empty()==0&&j-q.front()>a[i].l)
    				{
    					q.pop_front();
    				}
    				if(q.empty()==0)
    				{
    					f[i][j]=max(f[i][j],a[i].p*j+f[i-1][q.front()]-a[i].p*q.front());
    				}
    			}
    		}
    	}
    	cout<<f[m][n]<<endl;
    	return 0;
    }
    

luogu P10692 [SNCPC2024] 表达式矩阵

  • 大力结论:同一行/列中最多有两个 \(11\) ;符号尽可能用 \(\times\) 相连。

  • 当同一行/列中恰好有一个 \(11\) 时,使用 \(\times\)\(1\) 相连。

    • 行列大小同时为偶数。
  • 当同一行/列中包含两个 \(11\) 时,彼此所在的表达式用使用 \(+\) 相连。如有交点则斜着匹配。

    • 行列大小不同时为偶数。
  • 懒得剪枝,所以打表。

    点击查看代码
    int main()
    {
    	string b[100][100];
    	b[3][3]="1111*1111";
    	b[3][4]="11111*111111";
    	b[3][5]="111111*1*111111";
    	b[3][6]="1111111*1*11111111";
    	b[3][7]="11111111*1*1*11111111";
    	b[3][8]="111111111*1*1*1111111111";
    	b[3][9]="1111111111*1*1*1*1111111111";
    	b[4][3]="1111111*1111",
    	b[4][4]="11111*1111*11111";
    	b[4][5]="111111*1*111+1111111";
    	b[4][6]="11111111*1*11*1*11111111";
    	b[4][7]="11111111*1*1*111+1*111111111";
    	b[4][8]="111111111*1*1*1111*1*1*111111111";
    	b[4][9]="1111111111*1*1*1*111*1*1+11111111111";
    	b[5][3]="1111*11111*1111",
    	b[5][4]="11111*1111+11*111111",
    	b[5][5]="111111*1*111+111*1*111111";
    	b[5][6]="1111111*1*1111+1+11*1*11111111";
    	b[5][7]="11111111*1*1*111+1+111*1*1*11111111";
    	b[5][8]="111111111*1*1*1111+1+1+11*1*1*1111111111";
    	b[5][9]="1111111111*1*1*1*111+1+1+111*1*1*1*1111111111";
    	b[6][3]="1111111*11111*1111",
    	b[6][4]="11111*1111*11*1111*11111",
    	b[6][5]="1111111+111*1*111+111*1*111111",
    	b[6][6]="1111111*1*1111*1*11*1*1111*1*1111111";
    	b[6][7]="11111111*1*1*111+1*111*1*1*111+1*111111111";
    	b[6][8]="111111111*1*1*1111*1*1*11*1*1*1111*1*1*111111111";
    	b[6][9]="1111111111*1*1*1*111*1*1+111*1*1*1*111+1*1*11111111111";
    	b[7][3]="1111*11111*11111*1111",
    	b[7][4]="11111*1111*11*1111+11*111111",
    	b[7][5]="111111*1*111+111*1*111+111*1*111111",
    	b[7][6]="1111111*1*1111*1*11*1*1111+1+11*1*11111111",
    	b[7][7]="11111111*1*1*111+1*111*1*1*111*1+111*1*1*11111111";
    	b[7][8]="111111111*1*1*1111+1*1+11*1*1*1111*1+1*11*1*1*1111111111";
    	b[7][9]="1111111111*1*1*1*111*1*1+111*1*1*1*111+1+1*111*1*1*1*1111111111";
    	b[8][3]="1111111*11111*11111*1111";
    	b[8][4]="111111*11*1111*11*1111*11*111111",
    	b[8][5]="1111111+111*1*111+111*1*111+111*1*111111",
    	b[8][6]="11111111*1*11*1*1111*1*11*1*1111*1*11*1*11111111",
    	b[8][7]="111111111+1*111*1*1*111*1+111*1*1*111+1*111*1*1*11111111",
    	b[8][8]="111111111*1*1*1111*1*1*11*1*1*1111*1*1*11*1*1*1111*1*1*111111111";
    	b[8][9]="1111111111*1*1*1*111+1*1*111*1*1*1*111+1*1*111*1*1*1*111*1*1+11111111111";
    	b[9][3]="1111*11111*11111*11111*1111";
    	b[9][4]="11111*1111+11*1111*11*1111*11*111111",
    	b[9][5]="111111*1*111+111*1*111+111*1*111+111*1*111111",
    	b[9][6]="1111111*1*1111+1*11*1*1111*1*11*1*1111*1+11*1*11111111",
    	b[9][7]="11111111*1*1*111+1*111*1*1*111*1+111*1*1*111*1+111*1*1*11111111",
    	b[9][8]="111111111*1*1*1111*1*1+11*1*1*1111*1*1*11*1*1*1111+1+1*11*1*1*1111111111",
    	b[9][9]="1111111111*1*1*1*111+1*1*111*1*1*1*111*1+1*111*1*1*1*111*1*1+111*1*1*1*1111111111";
    	int n,m,i,j; 
    	cin>>n>>m;
    	string s=' '+b[n][m];
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			cout<<s[(i-1)*m+j];
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

luogu P1295 [TJOI2011] 书架

  • 多倍经验: luogu P1848 [USACO12OPEN] Bookshelf G

  • \(f_{i}\) 表示把前 \(i\) 个数分成若干段,在满足每段中所有数的和不超过 \(m\) 的前提下各段的最大值之和的最小值,状态转移方程为 \(f_{i}=\min\limits_{0 \le j<i-1 \land \sum\limits_{k=j+1}^{i}a_{k} \le m} \{ f_{j}+\max\limits_{k=j+1}^{i} \{ a_{k} \} \}\) ,其中随着 \(i\) 的增长 \(f_{i}\) 单调不降,随着 \(j\) 的增长 \(\max\limits_{k=j+1}^{i} \{ a_{k} \}\) 单调不增。

  • 我们猜测,当 \(j\) 处于转移边界时才可能成为最优决策,即在已知 \(\sum\limits_{k=j+1}^{i}a_{k} \le m\) 的情况下当 \(j\) 满足 \(\sum\limits_{k=j}^{i}a_{k} >m\)\(a_{j}=\max\limits_{k=j}^{i} \{ a_{k} \}\) 时才可能成为最优决策。问题来到了如何证明。

  • 考虑反证法。假设 \(j\) 满足 \(\sum\limits_{k=j}^{i}a_{k} \le m\)\(a_{j}<\max\limits_{k=j}^{i} \{ a_{k} \}=\max\limits_{k=j+1}^{i} \{ a_{k} \}\) 且可能成为最优决策。则对于 \(j-1\) 对答案的贡献为 \(f_{j-1}+\max\limits_{k=j}^{i} \{ a_{k} \}\) ,不大于 \(j\) 对答案的贡献为 \(f_{j}+\max\limits_{k=j+1}^{i} \{ a_{k} \}\) ,即 \(j-1\)\(j\) 更容易成为最优决策,与假设不符,故假设不成立。

  • 对于每一个 \(i\) 预处理出最小的 \(j\) 使得 \(\sum\limits_{k=j}^{i}a_{k} \le m\) 成立,记作 \(c_{i}\) 。首次计算 \(f_{i}\) 时从 \(c_{i}\) 进行一次转移。

  • 单调队列维护 \(a_{i}\) ,优先队列维护 \(f_{j}+\max\limits_{k=j+1}^{i} \{ a_{k} \}\) ,插入与删除(懒惰删除)是同时的。特别地,在加入 \(i\) 后,先前的决策 \(\{ j \}\)\(j_{|j|}\) 外对 \(\max\limits_{k=j+1}^{i-1} \{ a_{k} \}\)\(\max\limits_{k=j+1}^{i} \{ a_{k} \}\) 没有影响,但 \(j_{|j|}\) 可能会因为 \(a_{i}\) 的插入产生了影响,可重新加入贡献,询问时删去原数。

    点击查看代码
    ll a[100010],sum[100010],c[100010],f[100010],fmaxx[100010][25],vis[200010];
    struct node
    {
    	ll pos,maxx;
    	bool operator < (const node &another) const
    	{
    		return f[pos]+maxx>f[another.pos]+another.maxx;
    	}
    };
    deque<ll>q;
    priority_queue<node>e;
    void init(ll n,ll a[])
    {
    	for(ll i=1;i<=n;i++)
    	{
    		fmaxx[i][0]=a[i];
    	}
    	for(ll j=1;j<=log2(n);j++)
    	{
    		for(ll i=1;i<=n-(1<<j)+1;i++)
    		{
    			fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]);
    		}
    	}
    }
    ll query(ll l,ll r)
    {
    	ll t=log2(r-l+1);
    	return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]);
    }
    int main()
    {
    	ll n,m,flag=0,i;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		sum[i]=sum[i-1]+a[i];
    	}
    	if(flag==0)
    	{   
    		c[0]=1;
    		for(i=1;i<=n;i++)
    		{
    			for(c[i]=c[i-1];c[i]<=n;c[i]++)
    			{
    				if(sum[i]-sum[c[i]-1]<=m)
    				{
    					break;
    				}
    			}
    		}
    		init(n,a);
    		for(i=1;i<=n;i++)
    		{
    			f[i]=f[c[i]-1]+query(c[i],i);
    			while(q.empty()==0&&sum[i]-sum[q.front()]>m)
    			{
    				vis[q.front()]=1;
    				q.pop_front();
    			}
    			while(q.empty()==0&&a[q.back()]<a[i])
    			{
    				vis[q.back()]=1;
    				q.pop_back();
    			}
    			if(q.empty()==0)
    			{
    				e.push((node){q.back(),a[i]});
    			}
    			q.push_back(i);
    			while(e.empty()==0&&(vis[e.top().pos]==1||e.top().maxx!=query(e.top().pos+1,i)))
    			{
    				e.pop();
    			}
    			if(e.empty()==0)
    			{
    				f[i]=min(f[i],f[e.top().pos]+e.top().maxx);
    			}
    		}
    		cout<<f[n]<<endl;
    	}
    	else
    	{
    		cout<<"-1"<<endl;
    	}
    	return 0;
    }
    

[ABC361A] Insert

[ABC361F] x = a^b

[ABC361C] Make Them Narrow

[ABC361B] Intersection of Cuboids

7.7

闲话

  • 早上有体活(貌似只有新高一有),顺便去食堂给饭卡充钱。
  • 回机房时恰好碰到高二的下课去吃饭, \(huge\) 还没走。因 @int_R 以为有体活导致最晚到位, \(huge\) 让其查我们迟到。要求 \(7:30\) 到位,但让 \(7:31\) 开始查,迟到的在外面站着,最后说让 @int_R 不要徇私舞弊。
  • 上午还有小孩哥来上公益课。算上昨天的 \(4\) 个教练都讲了一轮,都搁这刷经验呢?
  • 志愿填报刚开始我妈就给我报上了,依次是 HZ 统招, HS 公助, FY 公助, HS 普惠, FY 普惠。但我考统招和公助基本没戏,好奇我妈为啥给我报这个。至少要是都考不上还有“卖身契”的 HS 自费。
  • 下午 \(miaomiao\) 给放了倍增优化 \(DP\) 和数据结构优化 \(DP\) 视频,说明天晚上前把倍增优化 \(DP\) ,数据结构优化 \(DP\) ,斜率优化 \(DP\) ,四边形不等式优化 \(DP\) 都写完。后天打模拟赛,但不一定考 \(DP\)
  • 晚上数奥教练来了趟机房找 \(field\) ,看我们的表情好像不太满意。
  • 晚上想打 Codeforces Round #956 (Div. 2) and ByteRace 2024 ,但 \(feifei\) 说因为高中的还没放假,等我们打完后就不能回宿舍了,所以就没让打,但暑假集训肯定让打。

做题纪要

[ABC361E] Tree and Hamilton Path 2

luogu P6154 游走

  • \(f_{x}\) 表示以 \(x\) 结尾的路径长度和, \(g_{x}\) 表示以 \(x\) 结尾的路径条数,状态转移方程为 \(\begin{cases} f_{x}=\sum\limits_{(y,x) \in E}f_{y}+g_{y} \\ g_{x}=1+\sum\limits_{(y,x) \in E} g_{y} \end{cases}\)

  • 因为是有向无环图,跑遍拓扑排序即可。

  • 最终有 \(\frac{\sum\limits_{i=1}^{n}f_{i}}{\sum\limits_{i=1}^{n}g_{i}}\) 即为所求。

    点击查看代码
    const ll p=998244353;
    struct node
    {
    	ll nxt,to;
    }e[1400010];
    ll head[1400010],din[1400010],f[1400010],g[1400010],cnt=0;
    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;
    }
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void top_sort(ll n)
    {
    	queue<ll>q;
    	ll x,i;
    	for(i=1;i<=n;i++)
    	{
    		if(din[i]==0)
    		{
    			q.push(i);
    		}
    	}
    	while(q.empty()==0)
    	{
    		x=q.front();
    		q.pop();
    		for(i=head[x];i!=0;i=e[i].nxt)
    		{
    			din[e[i].to]--;
    			f[e[i].to]=((f[e[i].to]+f[x])%p+g[x])%p;
    			g[e[i].to]=(g[e[i].to]+g[x])%p;
    			if(din[e[i].to]==0)
    			{
    				q.push(e[i].to);
    			}
    		}
    	}
    }
    int main()
    {
    	ll n,m,u,v,up=0,down=0,i;
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		din[v]++;
    	}
    	for(i=1;i<=n;i++)
    	{
    		g[i]=1;
    	}
    	top_sort(n);
    	for(i=1;i<=n;i++)
    	{
    		up=(up+f[i])%p;
    		down=(down+g[i])%p;
    	}
    	cout<<up*qpow(down,p-2,p)%p;
    	return 0;
    }
    

luogu P2569 [SCOI2010] 股票交易

  • \(f_{i,j}\) 表示第 \(i\) 天持有 \(j\) 股股票的最大钱数。

  • 考虑此时的操作一共有四种。

    • 凭空买(先前均是不买不卖),有 \(f_{i,j}=-ap_{i} \times j (j \in [0,ap_{i}])\)
    • 不买不卖,有 \(f_{i,j}=\max(f_{i,j},f_{i-1,j})(j \in [0,maxp])\)
    • 只买,有 \(f_{i,j}=\max(f_{i,j},\max\limits_{k=\max(0,j-as_{i})}^{j} \{ f_{i-w-1,k}-ap_{i} \times (j-k) \})=\max(f_{i,j},-ap_{i} \times j+\max\limits_{k=\max(0,j-as_{i})}^{j} \{ f_{i-w-1,k}+ap_{i} \times k \})\)
      • 单调队列维护 \(f_{i-w-1,k}+ap_{i} \times k\) 即可。
    • 只卖,有 \(f_{i,j}=\max(f_{i,j},\max\limits_{k=j}^{\min(maxp,j+bs_{i})} \{ f_{i-w-1,k}+bp_{i} \times (k-j) \})=\max(f_{i,j},-bp_{i} \times j+\max\limits_{k=j}^{\min(maxp,j+bs_{i})} \{ f_{i-w-1,k}+bp_{i} \times k \})\)
      • 单调队列维护 \(f_{i-w-1,k}+bp_{i} \times k\)
  • 最终,有 \(\max\limits_{i=0}^{maxp} \{ f_{t,i} \}\) 即为所求。

    点击查看代码
    int ap[2010],bp[2010],as[2010],bs[2010],f[2010][2010];
    deque<int>q;
    int val(int i,int w,int k,int p[])
    {
    	return f[i-w-1][k]+k*p[i];
    }
    int main()
    {
    	int t,maxp,w,ans=0,i,j;
    	cin>>t>>maxp>>w;
    	memset(f,-0x3f,sizeof(f));
    	for(i=1;i<=t;i++)
    	{
    		cin>>ap[i]>>bp[i]>>as[i]>>bs[i];
    	}
    	for(i=1;i<=t;i++)
    	{
    		for(j=0;j<=as[i];j++)
    		{
    			f[i][j]=max(f[i][j],-j*ap[i]);
    		}
    		for(j=0;j<=maxp;j++)
    		{
    			f[i][j]=max(f[i][j],f[i-1][j]);
    		}
    		if(i-w-1>=0)
    		{
    			q.clear();
    			for(j=0;j<=maxp;j++)
    			{
    				while(q.empty()==0&&j-q.front()>as[i])
    				{
    					q.pop_front();
    				}
    				if(q.empty()==0)
    				{
    					f[i][j]=max(f[i][j],-j*ap[i]+val(i,w,q.front(),ap));
    				}
    				while(q.empty()==0&&val(i,w,q.back(),ap)<=val(i,w,j,ap))
    				{
    					q.pop_back();
    				}
    				q.push_back(j);
    			}
    			q.clear();
    			for(j=maxp;j>=0;j--)
    			{
    				while(q.empty()==0&&q.front()-j>bs[i])
    				{
    					q.pop_front();
    				}
    				if(q.empty()==0)
    				{
    					f[i][j]=max(f[i][j],-j*bp[i]+val(i,w,q.front(),bp));
    				}
    				while(q.empty()==0&&val(i,w,q.back(),bp)<=val(i,w,j,bp))
    				{
    					q.pop_back();
    				}
    				q.push_back(j);
    			}
    		}
    	}
    	for(i=0;i<=maxp;i++)
    	{
    		ans=max(ans,f[t][i]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P3089 [USACO13NOV] Pogo-Cow S

  • 将目标点按照 \(x\) 升序排序。

  • \(f_{i,j}\) 表示一直向右跳时,最后一步是从第 \(i\) 个点跳到了第 \(j\) 个点的最大得分,状态转移方程为 \(f_{i,j}=p_{j}+\max\limits_{k=1}^{i} \{ [x_{j}-x_{i} \ge x_{i}-x_{k}] \times f_{k,i} \}\) ,移项有 \(f_{i,j}=p_{j}+\max\limits_{k=1}^{i} \{ [2x_{i}-x_{j} \le x_{k}] \times f_{k,i} \}\) ,边界为 \(f_{i,i}=p_{i}\) 。向左跳同理。

  • 单调队列维护 \(f_{k,i}\) 即可。

    点击查看代码
    int f[2010][2010],g[2010][2010];
    deque<int>q;
    int main()
    {
    	int n,ans=0,i,j,k;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i].first>>a[i].second;
    	}
    	sort(a+1,a+1+n);
    	for(i=1;i<=n;i++)
    	{
    		q.clear();
    		f[i][i]=a[i].second;
    		for(k=1;k<=i;k++)
    		{
    			while(q.empty()==0&&f[q.back()][i]<=f[k][i])
    			{
    				q.pop_back();
    			}
    			q.push_back(k);
    		}
    		for(j=n;j>=i+1;j--)
    		{
    			f[i][j]=a[j].second;
    			while(q.empty()==0&&2*a[i].first-a[j].first>a[q.front()].first)
    			{
    				q.pop_front();
    			}
    			f[i][j]+=f[q.front()][i];//q 中一定至少含有一个元素 i
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		a[i].first=-a[i].first;
    	}
    	sort(a+1,a+1+n);
    	for(i=1;i<=n;i++)
    	{
    		q.clear();
    		g[i][i]=a[i].second;
    		for(k=1;k<=i;k++)
    		{
    			while(q.empty()==0&&g[q.back()][i]<=g[k][i])
    			{
    				q.pop_back();
    			}
    			q.push_back(k);
    		}
    		for(j=n;j>=i+1;j--)
    		{
    			g[i][j]=a[j].second;
    			while(q.empty()==0&&2*a[i].first-a[j].first>a[q.front()].first)
    			{
    				q.pop_front();
    			}
    			g[i][j]+=g[q.front()][i];//q 中一定至少含有一个元素 i
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			ans=max(ans,max(f[i][j],g[i][j]));
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P10713 【MX-X1-T1】「KDOI-05」简单的无限网格问题

  • \((1,1)\)\((n,m)\) 的水平距离为 \(n-1\) ,竖直距离为 \(m-1\) 。又因为奇数加奇数等于偶数,故当 \(n-1\)\(m-1\) 奇偶性相同时,最少操作 \(3\) 次;否则操作 \(2\) 次。

    点击查看代码
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		cout<<(((n-1)%2==(m-1)%2)?3:2)<<endl;
    	}
    	return 0;
    }
    

luogu P2254 [NOI2005] 瑰丽华尔兹

  • \(f_{i,j,k}\) 表示第 \(i\) 段时间滑到 \((j,k)\) 时最长的滑行距离,状态转移方程为 \(f_{i,j,k}=\max\limits \{ f_{i-1,j',k'}+|j'-j|+|k'-k| \}\)

  • 对四个方向进行分讨。

    点击查看代码
    int s[210],t[210],d[210],f[2][210][210],dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};
    char c[210][210];
    deque<int>q;
    void dp(int pos,int s,int t,int d,int n,int m)
    {
    	if(d==1)
    	{
    		for(int j=1;j<=m;j++)
    		{   
    			q.clear();
    			for(int i=n;i>=1;i--)
    			{
    				if(c[i][j]=='x')
    				{
    					q.clear();
    				}
    				else
    				{
    					f[pos&1][i][j]=max(f[pos&1][i][j],f[(pos-1)&1][i][j]);
    					while(q.empty()==0&&q.front()-i>t-s+1)
    					{
    						q.pop_front();
    					}
    					if(q.empty()==0)
    					{
    						f[pos&1][i][j]=max(f[pos&1][i][j],-i+f[(pos-1)&1][q.front()][j]+q.front());
    					}
    					while(q.empty()==0&&f[(pos-1)&1][q.back()][j]+q.back()-i<=f[(pos-1)&1][i][j])
    					{
    						q.pop_back();
    					}
    					q.push_back(i);
    				}
    			}
    		}
    	}
    	if(d==2)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			q.clear();
    			for(int i=1;i<=n;i++)
    			{
    				if(c[i][j]=='x')
    				{
    					q.clear();
    				}
    				else
    				{
    					f[pos&1][i][j]=max(f[pos&1][i][j],f[(pos-1)&1][i][j]);
    					while(q.empty()==0&&i-q.front()>t-s+1)
    					{
    						q.pop_front();
    					}
    					if(q.empty()==0)
    					{
    						f[pos&1][i][j]=max(f[pos&1][i][j],i+f[(pos-1)&1][q.front()][j]-q.front());
    					}
    					while(q.empty()==0&&f[(pos-1)&1][q.back()][j]+i-q.back()<=f[(pos-1)&1][i][j])
    					{
    						q.pop_back();
    					}
    					q.push_back(i);
    				}
    			}
    		}
    	}
    	if(d==3)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			q.clear();
    			for(int j=m;j>=1;j--)
    			{
    				if(c[i][j]=='x')
    				{
    					q.clear();
    				}
    				else
    				{
    					f[pos&1][i][j]=max(f[pos&1][i][j],f[(pos-1)&1][i][j]);
    					while(q.empty()==0&&q.front()-j>t-s+1)
    					{
    						q.pop_front();
    					}
    					if(q.empty()==0)
    					{
    						f[pos&1][i][j]=max(f[pos&1][i][j],-j+f[(pos-1)&1][i][q.front()]+q.front());
    					}
    					while(q.empty()==0&&f[(pos-1)&1][i][q.back()]+q.back()-j<=f[(pos-1)&1][i][j])
    					{
    						q.pop_back();
    					}
    					q.push_back(j);
    				}
    			}
    		}
    	}
    	if(d==4)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			q.clear();
    			for(int j=1;j<=m;j++)
    			{
    				if(c[i][j]=='x')
    				{
    					q.clear();
    				}
    				else
    				{
    					f[pos&1][i][j]=max(f[pos&1][i][j],f[(pos-1)&1][i][j]);
    					while(q.empty()==0&&j-q.front()>t-s+1)
    					{
    						q.pop_front();
    					}
    					if(q.empty()==0)
    					{
    						f[pos&1][i][j]=max(f[pos&1][i][j],j+f[(pos-1)&1][i][q.front()]-q.front());
    					}
    					while(q.empty()==0&&f[(pos-1)&1][i][q.back()]+j-q.back()<=f[(pos-1)&1][i][j])
    					{
    						q.pop_back();
    					}
    					q.push_back(j);
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	int n,m,x,y,k,ans=0,i,j;
    	cin>>n>>m>>x>>y>>k;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			cin>>c[i][j];
    		}
    	}
    	memset(f[0],-0x3f,sizeof(f[0]));
    	f[0][x][y]=0;
    	for(i=1;i<=k;i++)
    	{
    		cin>>s[i]>>t[i]>>d[i];
    		memset(f[i&1],-0x3f,sizeof(f[i&1]));
    		dp(i,s[i],t[i],d[i],n,m);
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			ans=max(ans,f[k&1][i][j]);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P1776 宝物筛选

  • 单调队列优化多重背包板子。

  • \(f_{i,j}\) 表示到第 \(i\) 个物品时,已经装了的物品的容量为 \(j\) 时的最大价值。

  • \(01\) 背包看作 \(num_{i}=1\) 的多重背包。状态转移方程为 \(f_{i,j}=\max\limits_{k=0}^{num_{i}} \{ f_{i-1,j-k \times v_{i}}+k \times w_{i} \}\)

    • 观察到状态由 \(j-v_{i}\)\(j\) 的过程中,决策集合中有一个新决策被加入,一个旧决策被删除。所以可以将状态 \(j\) 按照 \(\bmod v_{i}\) 的结果分组,不同组间的 \(j\) 不会相互影响。
    • 由原来的枚举 \(j \in [v_{i},m]\) 改为 \(r \in [0,v_{i})\)\(h \in [0,\left\lfloor \frac{m-r}{v_{i}} \right\rfloor]\) 表示装了 \(h\)\(i\) 种物品,从而使得 \(j\) 能表示成 \(j=h \times v_{i}+r\) 的形式。状态转移方程为 \(f_{i,h \times v_{i}+r}=\max\limits_{k=\max(0,h-num_{i})}^{h} \{ f_{i-1,k \times v_{i}+r}+(h-k) \times w_{i} \}=h \times w_{i}+\max\limits_{k=\max(0,h-num_{i})}^{h} \{ f_{i-1,k \times v_{i}+r}-k \times w_{i} \}\)
    • 单调队列维护 \(f_{i-1,k \times v_{i}+r}-k \times w_{i}\) 即可。
    点击查看代码
    ll v[100010],w[100010],num[100010],f[2][100010];
    deque<ll>q;
    int main()
    {
    	ll n,m,ans=0,i,j,k,h,r;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>w[i]>>v[i]>>num[i];
    		for(r=0;r<=v[i]-1;r++)
    		{
    			q.clear();
    			for(h=0;h<=(m-r)/v[i];h++)
    			{
    				f[i&1][h*v[i]+r]=f[(i-1)&1][h*v[i]+r];
    				while(q.empty()==0&&h-q.front()>num[i])
    				{
    					q.pop_front();
    				}
    				if(q.empty()==0)
    				{
    					f[i&1][h*v[i]+r]=max(f[i&1][h*v[i]+r],h*w[i]+f[(i-1)&1][q.front()*v[i]+r]-q.front()*w[i]);
    				}
    				while(q.empty()==0&&f[(i-1)&1][q.back()*v[i]+r]-q.back()*w[i]<=f[(i-1)&1][h*v[i]+r]-h*w[i])
    				{
    					q.pop_back();
    				}
    				q.push_back(h);
    			}
    		}
    	}
    	for(i=0;i<=m;i++)
    	{
    		ans=max(ans,f[n&1][i]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

牛客 NC275564 小红的最小最大

  • 选择结构。

    点击查看代码
    int main()
    {
    	ll a,b,x;
    	cin>>a>>b>>x;
    	if(min(a,b)+x>max(a,b))
    	{
    		cout<<"YES"<<endl;
    	}
    	else
    	{
    		cout<<"NO"<<endl;
    	}
    	return 0;
    }
    

luogu P4644 [USACO05DEC] Cleaning Shifts S

  • 多倍经验: luogu P1668 [USACO04DEC] Cleaning Shifts S

  • 将奶牛按照右端点升序排序。

  • 整条线段整体向右移两位。

  • \(f_{x}\) 表示覆盖 \([l,x]\) 需要花费的最小代价,状态转移方程为 \(f_{r_{i}}=\min(f'_{r_{i}},s_{i}+\min\limits_{j=l_{i}-1}^{r_{i}-1} \{ f_{j} \})\) ,边界为 \(f_{l}=0\)

  • 维护一个单点修改、区间查询 \(\min\) 的线段树即可。

    点击查看代码
    struct node
    {
    	int l,r,s;
    }a[250010];
    int f[250010];
    bool cmp(node a,node b)
    {
    	return a.r<b.r;
    }
    struct SMT
    {
    	struct SegmentTree
    	{
    		int l,r,f,minn;
    	}tree[1000010];
    	int lson(int x)
    	{
    		return x*2;
    	}
    	int rson(int x)
    	{
    		return x*2+1;
    	}
    	void pushup(int rt)
    	{
    		tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn);
    	}
    	void build(int rt,int l,int r)
    	{
    		tree[rt].l=l;
    		tree[rt].r=r;
    		if(l==r)     
    		{
    			tree[rt].f=tree[rt].minn=f[l];
    			return;
    		}
    		int mid=(l+r)/2;
    		build(lson(rt),l,mid);
    		build(rson(rt),mid+1,r);
    		pushup(rt);
    	}
    	void update(int rt,int pos,int val)
    	{
    		if(tree[rt].l==tree[rt].r)
    		{
    			tree[rt].f=tree[rt].minn=min(tree[rt].f,val);
    			return;
    		}
    		int mid=(tree[rt].l+tree[rt].r)/2;
    		if(pos<=mid)
    		{
    			update(lson(rt),pos,val);
    		}   
    		else
    		{
    			update(rson(rt),pos,val);
    		}
    		pushup(rt);
    	}
    	int query(int rt,int x,int y)
    	{
    		if(x<=tree[rt].l&&tree[rt].r<=y)
    		{
    			return tree[rt].minn;
    		}
    		int mid=(tree[rt].l+tree[rt].r)/2,ans=0x7f7f7f7f;
    		if(x<=mid)
    		{
    			ans=min(ans,query(lson(rt),x,y));
    		}
    		if(y>mid)
    		{
    			ans=min(ans,query(rson(rt),x,y));
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    	int n,m,e,i;
    	cin>>n>>m>>e;
    	m+=2;
    	e+=2;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i].l>>a[i].r>>a[i].s;
    		a[i].l+=2;
    		a[i].r+=2;
    	}
    	sort(a+1,a+1+n,cmp);
    	memset(f,0x3f,sizeof(f));
    	f[m-1]=0;
    	T.build(1,1,e);
    	for(i=1;i<=n;i++)
    	{
    		f[a[i].r]=min(f[a[i].r],a[i].s+T.query(1,a[i].l-1,a[i].r-1));
    		T.update(1,a[i].r,f[a[i].r]);
    	}
    	cout<<((f[e]==0x3f3f3f3f)?-1:f[e])<<endl;
    	return 0;
    }
    

UVA12983 The Battle of Chibi

  • 多倍经验: CF597C Subsequences

  • \(f_{i,j}\) 表示前 \(i\) 个数中以 \(a_{i}\) 结尾的长度为 \(j\) 的严格上升子序列,状态转移方程为 \(f_{i,j}=\sum\limits_{k=0}^{i-1}[a_{k}<a_{i}] \times f_{k,j-1}\) ,边界为 \(f_{i,1}=1(i \in [1,n])\)

  • 对于每个 \(j\) 均建立一棵权值树状数组维护。

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

    点击查看代码
    const ll p=1000000007;
    ll b[1010],a[1010],f[1010][1010];
    struct BIT
    {
    	ll c[1010];
    	void init()
    	{
    		memset(c,0,sizeof(c));
    	}
    	ll lowbit(ll x)
    	{
    		return (x&(-x));
    	}
    	void add(ll n,ll x,ll val)
    	{
    		for(ll i=x;i<=n;i+=lowbit(i))
    		{
    			c[i]=(c[i]+val)%p;
    		}
    	}
    	ll ask(ll x)
    	{
    		ll ans=0;
    		for(ll i=x;i>=1;i-=lowbit(i))
    		{
    			ans=(ans+c[i])%p;
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    	ll t,n,m,ans=0,k,i,j;
    	cin>>t;
    	for(k=1;k<=t;k++)
    	{
    		cin>>n>>m;
    		ans=0;
    		memset(f,0,sizeof(f));
    		for(i=1;i<=n;i++)
    		{
    			cin>>b[i];
    			a[i]=b[i];
    			f[i][1]=1;
    		}
    		sort(b+1,b+1+n);
    		b[0]=unique(b+1,b+1+n)-(b+1);
    		for(i=1;i<=n;i++)
    		{
    			a[i]=lower_bound(b+1,b+1+b[0],a[i])-b;
    		}
    		for(j=2;j<=m;j++)
    		{
    			T.init();
    			for(i=1;i<=n;i++)
    			{
    				
    				f[i][j]=T.ask(a[i]-1);
    				T.add(b[0],a[i],f[i][j-1]);
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			ans=(ans+f[i][m])%p;
    		}
    		cout<<"Case #"<<k<<": "<<ans<<endl;
    	}
    	return 0;
    }
    

牛客 NC275574 小红的四则运算(hard)

  • 由于都是正整数, \(-\)\(\div\) 一定对答案不优。枚举两个位置的填符号情况依次判断。

  • 最终,有 \(\max(a+b+c,a \times b \times c,(a+b) \times c,a \times (b+c))\) 即为所求。

    点击查看代码
    int main()
    {
    	ll a[4];
    	cin>>a[1]>>a[2]>>a[3];
    	cout<<max(a[1]+a[2]+a[3],max(a[1]*a[2]*a[3],max((a[1]+a[2])*a[3],a[1]*(a[2]+a[3]))))<<endl;
    	return 0;
    }
    

牛客 NC275568 小红的四则运算(easy)

  • 生成全排列计算每种情况即可。

    点击查看代码
    int main()
    {
    	ll a[4],ans=0;
    	cin>>a[1]>>a[2]>>a[3];
    	sort(a+1,a+1+3);
    	do
    	{
    		ans=max(ans,max(a[1]+a[2]+a[3],max(a[1]*a[2]*a[3],max((a[1]+a[2])*a[3],a[1]*(a[2]+a[3])))));
    	}while(next_permutation(a+1,a+1+3));
    	cout<<ans<<endl;
    	return 0;
    }
    

7.8

闲话

  • 早上遇见了 HZ 组织第四届基础教育中外论坛,我们因为吃饭早自然成为了被校外老师提问的对象,问题包括年级、开学时间、现在来 HZ 干什么、报考 HZ 系的哪所学校、在 HZ 系感觉怎么样。
  • \(miaomiao\) 上午突然通知下午加练一场模拟赛,明天的模拟赛挪至后天。然后 \(huge\) 搬来了两个西瓜,因为剪发的缘故看起来以为是 \(bobo\)
  • 午休颓《平凡的世界》。
  • 下午 \(14:00 \sim 18:00\) \(miaomiao\) 安排了一场模拟赛,貌似还是和迪茵公学的同步赛。
  • \(18:05\) 提前去吃饭,晚到了会儿楼下,在 \(18:08\) 出了楼门,碰见了 \(field\)\(field\) 问我们为什么现在就下来吃饭,问我们晚上几点吃饭,骗他说 \(18:05\) 吃饭,他没再管,骑着电动车走了。
  • 吃完晚饭打算坐电梯的时候,还没按按钮电梯就显示向下了,以为是自动识别遂没太在意,等到电梯中走出人时还吓了一跳,她(指电梯中走出的人)说“我有这么吓人吗?看见你也把我吓了一跳”。
  • 晚上 @STA_Morlin 水谷被 \(feifei\) 抓了。
  • 回宿舍的路上偶然看到外面的社团宣传栏上,智慧手机器人社团的宣传照片上有 \(field\)
  • 晚休是现班主任和 \(field\) 查宿。

做题纪要

luogu P6087 [JSOI2015] 送礼物

  • 若没有 \(r-l+1 \ge L\) 的限制,当极值在区间内部时,缩小区间长度一定更优,故考虑将极值放在区间两头。

  • 当极值距离 \(<L\) 时直接将长度扩展到 \(L\) 是最优的,分母为 \(L-1+k\)

  • 当极值距离 \(\ge L\) 时考虑 \(0/1\) 分数规划,设二分的答案为 \(mid\) ,当 \(\frac{|a_{r}-a_{l}|}{r-l+k} \ge mid\) 时,若 \(a_{r}>a_{l}\)\((a_{r}-r \times mid)-(a_{l}-l \times mid) \ge k \times mid\) 否则有 \((a_{l}+l \times mid)-(a_{r}+r \times mid) \ge k \times mid\)

  • 三种情况分别用单调队列维护即可。

    点击查看代码
    double a[50010],c[50010];
    const double eps=1e-8;
    deque<int>qmax,qmin,q;
    bool check(double mid,int n,int k,int x,int y)
    {
    	q.clear();
    	for(int i=1;i<=n;i++)
    	{
    		c[i]=a[i]-mid*i;
    	}
    	for(int i=x;i<=n;i++)
    	{
    		if(c[i]-c[i-x+1]>=k*mid)
    		{
    			return true;
    		}
    		while(q.empty()==0&&i-q.front()>=y)
    		{
    			q.pop_front();
    		}
    		if(q.empty()==0)
    		{
    			if(c[i]-c[q.front()]>=k*mid)
    			{
    				return true;
    			}
    		}   
    		while(q.empty()==0&&c[q.back()]>=c[i-x+1])
    		{
    			q.pop_back();
    		}
    		q.push_back(i-x+1);
    	}
    	q.clear();
    	for(int i=1;i<=n;i++)
    	{
    		c[i]=a[i]+mid*i;
    	}
    	for(int i=x;i<=n;i++)
    	{
    		if(c[i-x+1]-c[i]>=k*mid)
    		{
    			return true;
    		}
    		while(q.empty()==0&&i-q.front()>=y)
    		{
    			q.pop_front();
    		}
    		if(q.empty()==0)
    		{
    			if(c[q.front()]-c[i]>=k*mid)
    			{
    				return true;
    			}
    		}   
    		while(q.empty()==0&&c[q.back()]<=c[i-x+1])
    		{
    			q.pop_back();
    		}
    		q.push_back(i-x+1);
    	}
    	return false;
    }
    int main()
    {
    	int t,n,k,x,y,i,j;
    	double l,r,mid,ans;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n>>k>>x>>y;
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    		}
    		ans=0;
    		qmax.clear();
    		qmin.clear();
    		for(i=1;i<=x-1;i++)
    		{
    			while(qmax.empty()==0&&a[qmax.back()]<=a[i])
    			{
    				qmax.pop_back();
    			}
    			qmax.push_back(i);
    			while(qmin.empty()==0&&a[qmin.back()]>=a[i])
    			{
    				qmin.pop_back();
    			}
    			qmin.push_back(i);
    		}
    		for(i=x;i<=n;i++)
    		{
    			while(qmax.empty()==0&&i-qmax.front()>=x)
    			{
    				qmax.pop_front();
    			}
    			while(qmin.empty()==0&&i-qmin.front()>=x)
    			{
    				qmin.pop_front();
    			}
    			if(qmax.empty()==0&&qmin.empty()==0)
    			{
    				ans=max(ans,(a[qmax.front()]-a[qmin.front()])/(1.0*(x-1+k)));
    			}
    			while(qmax.empty()==0&&a[qmax.back()]<=a[i])
    			{
    				qmax.pop_back();
    			}
    			qmax.push_back(i);
    			while(qmin.empty()==0&&a[qmin.back()]>=a[i])
    			{
    				qmin.pop_back();
    			}
    			qmin.push_back(i);
    		}
    		l=0;
    		r=1000;
    		while(r-l>eps)
    		{
    			mid=(l+r)/2;
    			if(check(mid,n,k,x,y)==true)
    			{
    				l=mid;
    				ans=max(ans,l);
    			}
    			else
    			{
    				r=mid;
    			}
    		}
    		printf("%.4lf\n",ans);
    	}
    	return 0;
    }
    

luogu P1081 [NOIP2012 提高组] 开车旅行

  • 考虑倍增优化 \(DP\)

  • 预处理

    • 平衡树预处理出小 \(\text{A}\) 和小 \(\text{B}\) 从第 \(i\) 个城市出发沿前进方向(东)行驶到的下一个城市,分别记为 \(ga_{i}\)\(gb_{i}\) ,同 luogu P10466 邻值查找
    • \(f_{i,j,0/1}\) 表示从城市 \(j\) 出发,两人共行驶 \(2^{i}\) 天,小 \(\text{A}\) /小 \(\text{B}\) 先开车,最终会到达的城市。状态转移方程为 \(\begin{cases} f_{i,j,k}=f_{i-1,f_{i-1,j,k},k \bigoplus 1} & i=1 \\ f_{i,j,k}=f_{i-1,f_{i-1,j,k},k} & i>1 \end{cases}\) ,边界为 \(\begin{cases} f_{0,j,0}=ga_{j} \\ f_{0,j,1}=gb_{j} \end{cases}\)
    • \(g_{i,j,0/1,0/1}\) 表示从城市 \(j\) 出发,两人共行驶 \(2^{i}\) 天,小 \(\text{A}\) /小 \(\text{B}\) 先开车,小 \(\text{A}\) /小 \(\text{B}\) 行驶路程的总长度。状态转移方程为 \(\begin{cases} g_{i,j,k,l}=g_{i-1,j,k,l}+g_{i-1,f_{i-1,j,k},k \bigoplus 1,l} & i=1 \\ g_{i,j,k,l}=g_{i-1,j,k,l}+g_{i-1,f_{i-1,j,k},k,l} & i>1 \end{cases}\) ,边界为 \(\begin{cases} g_{0,j,0,0}=d_{j,ga_{j}} \\ g_{0,j,1,0}=0 \\ g_{0,j,0,1}=0 \\ g_{0,j,1,1}=d_{j,gb_{j}} \end{cases}\)
  • 拼凑

    • 类似二进制拆分的逆过程。
    • 对于每组询问 \(s,x\) ,均按递减顺序枚举所有 \(2\) 的整数次幂来拼凑出行驶天数,进而拼成 \(x\) (不要求等于 \(x\) )。
    点击查看代码
    ll h[100010],ga[100010],gb[100010],f[20][100010][2],g[20][100010][2][2],N;
    multiset<pair<ll,ll> >e;
    multiset<pair<ll,ll> >::iterator it;
    int main()
    {
    	ll n,m,s,x,pos=0,ss,la,lb,i,j,k,l;
    	pair<ll,ll>pre,suf,prepre,sufsuf;
    	double ans=0x7f7f7f7f;
    	cin>>n;
    	N=log2(n)+1;
    	h[0]=0x7f7f7f7f;
    	e.insert(make_pair(0x7f7f7f7f,0));
    	e.insert(make_pair(0x7f7f7f7f,0));
    	for(i=1;i<=n;i++)
    	{
    		cin>>h[i];
    	}
    	h[n+1]=-0x7f7f7f7f;
    	e.insert(make_pair(-0x7f7f7f7f,n+1));
    	e.insert(make_pair(-0x7f7f7f7f,n+1));
    	for(i=n;i>=1;i--)
    	{
    		it=e.upper_bound(make_pair(h[i],i));
    		suf=*it;
    		it++;
    		sufsuf=*it;
    		it--;
    		it--;
    		pre=*it;
    		it--;
    		prepre=*it;
    		if(suf.first-h[i]<h[i]-pre.first)
    		{
    			gb[i]=suf.second;
    			ga[i]=(sufsuf.first-h[i]<h[i]-pre.first)?sufsuf.second:pre.second;
    		}
    		else 
    		{
    			gb[i]=pre.second;
    			ga[i]=(suf.first-h[i]<h[i]-prepre.first)?suf.second:prepre.second;
    		}
    		f[0][i][0]=ga[i];
    		f[0][i][1]=gb[i];
    		g[0][i][0][0]=abs(h[i]-h[ga[i]]);
    		g[0][i][1][1]=abs(h[i]-h[gb[i]]);
    		e.insert(make_pair(h[i],i));
    	}
    	for(i=1;i<=N;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			for(k=0;k<=1;k++)
    			{
    				f[i][j][k]=f[i-1][f[i-1][j][k]][(i==1)?(k^1):k];
    				for(l=0;l<=1;l++)
    				{
    					g[i][j][k][l]=g[i-1][j][k][l]+g[i-1][f[i-1][j][k]][(i==1)?(k^1):k][l];
    				}
    			}
    		}
    	}
    	cin>>x>>m;
    	for(s=1;s<=n;s++)
    	{
    		ss=s;
    		la=lb=0;
    		for(i=N;i>=0;i--)
    		{
    			if(f[i][ss][0]!=0&&la+lb+g[i][ss][0][0]+g[i][ss][0][1]<=x)
    			{
    				la+=g[i][ss][0][0];
    				lb+=g[i][ss][0][1];
    				ss=f[i][ss][0];
    			}
    		}
    		if(1.0*la/lb<ans)
    		{
    			ans=1.0*la/lb;
    			pos=s;
    		}
    		else
    		{
    			if(1.0*la/lb==ans&&h[s]>h[pos])
    			{
    				pos=s;
    			}
    		}
    	}
    	cout<<pos<<endl;
    	for(j=1;j<=m;j++)
    	{
    		cin>>s>>x;
    		ss=s;
    		la=lb=0;
    		for(i=N;i>=0;i--)
    		{
    			if(f[i][ss][0]!=0&&la+lb+g[i][ss][0][0]+g[i][ss][0][1]<=x)
    			{
    				la+=g[i][ss][0][0];
    				lb+=g[i][ss][0][1];
    				ss=f[i][ss][0];
    			}
    		}
    		cout<<la<<" "<<lb<<endl;
    	}
    	return 0;
    }
    

GHzoj 3752. 分糖果

luogu U194768 Count The Repetitions

  • 规定本题中字符串下标从 \(0\) 开始。

  • \(conn(conn(s_{2},n_{2}),m)\) 等价于 \(conn(s_{2},n_{2} \times m)\) 。故求出最大的 \(m'\) 使得 \(conn(s_{2},m')\) 能被 \(conn(s_{1},n_{1})\) 生成,则 \(m=\left\lfloor \dfrac{m'}{n_{2}} \right\rfloor\) 即为所求。

  • \(m'\) 上界为 \(\left\lfloor \dfrac{|s_{1}| \times n_{1}}{|s_{2}|} \right\rfloor\) ,考虑对其进行二进制拆分,设 \(m'\) 在二进制表示下数位上数字为 \(1\) 的数位分别为 \(c_{1},c_{2}, \dots c_{t}\) ,即 \(m'=\sum\limits_{i=1}^{t}2^{c_{i}}\) ,表示 \(conn(s_{2},m')\) 是由 \(conn(s_{2},2^{c_{1}}),conn(s_{2},2^{c_{2}}), \dots ,conn(s_{2},2^{c_{t}})\) 首尾拼接而成的。

  • 预处理

    • 假设 \(n_{1}\) 足够大,即 \(s_{1}\) 无限长时进行预处理。设 \(f_{i,j}\) 表示从 \(s_{1,i}\) 开始至少需要多少个字符才能生成 \(conn(s_{2},2^{j})\) ,状态转移方程为 \(f_{i,j}=f_{i,j-1}+f_{(i+f_{i,j-1}) \bmod |s_{1}|,j-1}\) ,边界 \(f_{i,0}\) 需要特殊处理。
  • 拼接

    • 按递增顺序枚举起始位置 \(i\) ,按递减顺序枚举指数 \(j\) ,不断移动位置判断。
    点击查看代码
    ll f[200][30],N;
    char s1[200],s2[200];
    int main()
    {
    	ll n1,n2,len1,len2,m,pos,cnt,flag,i,j;
    	while(cin>>s2>>n2>>s1>>n1)
    	{
    		len1=strlen(s1);
    		len2=strlen(s2);
    		m=flag=0;    
    		N=log2(len1*n1/len2)+1;
    		for(i=0;i<=len1-1;i++)
    		{
    			f[i][0]=0;
    			pos=i;
    			for(j=0;j<=len2-1;j++)
    			{
    				cnt=1;
    				while(s1[pos]!=s2[j])
    				{
    					pos=(pos+1)%len1;
    					cnt++;
    					if(cnt>=len1+1)//如果 s2 中出现了 s1 中没有出现过的元素则无解
    					{
    						flag=1;
    						break;
    					}
    				}
    				if(flag==1)
    				{
    					break;
    				}
    				pos=(pos+1)%len1;//走到下一位
    				f[i][0]+=cnt;
    			}
    			if(flag==1)
    			{
    				break;
    			}
    		}
    		if(flag==0)
    		{
    			for(j=1;j<=N;j++)
    			{
    				for(i=0;i<=len1-1;i++)
    				{
    					f[i][j]=f[i][j-1]+f[(i+f[i][j-1])%len1][j-1];
    				}
    			}
    			for(i=0;i<=len1-1;i++)
    			{
    				pos=i;
    				cnt=0;
    				for(j=N;j>=0;j--)
    				{
    					if(pos+f[pos%len1][j]<=len1*n1)
    					{
    						pos+=f[pos%len1][j];
    						cnt+=(1<<j);
    					}
    				}
    				m=max(m,cnt);
    			}
    		}
    		cout<<m/n2<<endl;
    	}
    	return 0;
    }
    

luogu P2365 任务安排

  • 观察到机器因执行本批任务而花费的启动时间 \(s\) 会累加到在此之后所有任务的完成时刻上,故我们可以在一批任务的开始时就将其对后续任务产生的影响累加到答案中,而不是后续再统计先前的影响,称之为“费用提前计算”。

  • \(f_{i}\) 表示将前 \(i\) 个任务分成若干批执行的最小费用,状态转移方程为 \(f_{i}=\min\limits_{j=0}^{i-1} \{ f_{j}+(\sum\limits_{k=1}^{i}t_{k}) \times (\sum\limits_{k=j+1}^{i}c_{i})+s \times (\sum\limits_{k=j+1}^{n}c_{k}) \}\) ,边界为 \(f_{0}=0\)

    点击查看代码
    ll t[5010],c[5010],sumt[5010],sumc[5010],f[5010];
    int main()
    {
    	ll n,s,i,j;
    	cin>>n>>s;
    	for(i=1;i<=n;i++)
    	{
    		cin>>t[i]>>c[i];
    		sumt[i]=sumt[i-1]+t[i];
    		sumc[i]=sumc[i-1]+c[i];
    	}
    	memset(f,0x3f,sizeof(f));
    	f[0]=0;
    	for(i=1;i<=n;i++)
    	{
    		for(j=0;j<=i-1;j++)
    		{
    			f[i]=min(f[i],f[j]+sumt[i]*(sumc[i]-sumc[j])+s*(sumc[n]-sumc[j]));
    		}
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

GHzoj 3754. 与或

GHzoj 3755. 跳舞

7.9

闲话

  • 起床来机房后 \(huge\) 突然进来问我们今天谁值日,通报了下宿舍卫生情况,包括但不限于柜子上的奶盒,没有拖地,左门上床上有杂乱东西,右门下、右中下蚊帐没收拾,等他们去整改后 \(huge\) 跟我们说暑假集训快到了,宿舍内务也得好好搞,后面要是再出现这个问题就让回去半天整改,初中三年养成的习惯不能因为一个暑假集训就废掉了。
  • 吃完早饭 \(huge\) 又进来强调了下内务的问题。
  • 去楼下上厕所的时候碰见原英语老师了,打了个招呼。貌似 \(4\) 楼是语文和英语老师的备课区。
  • 上午因讨论时间(其实大部分是闲聊)过长、声音过大被 \(huge\) \(D\) 了,说讨论时间超过 \(5 \min\) 的拿着纸笔去外面讨论,和初三现班主任的要求一样;现在在机房是远离级部,没人管,要是后续搬到教学楼的话就按照年级部的要求走了,上课时间不允许随意说话。然后 \(miaomiao\) 说了下以后沉浸式刷题、讨论时间、独立刷题、总结时间的安排,感觉有点 \(whk\) 入侵机房似的,有点不适应,而且为什么要把刷题分成沉浸式刷题、独立刷题和自我刷题呢;讲了下独立做题的好处,避免考试考到原题调了却不会做。
  • 中午给家里打电话的时候说了下丢了多少东西。午休颓《平凡的世界》。
  • 下午组织线上讲题。 \(miaomiao\) 说这几天就是一边考试一边过知识点,放假前把斜率优化 \(DP\) 和四边形不等式优化 \(DP\) 写完。
  • 临近吃晚饭时眼睛腿彻底断了,用胶带缠上了,只能每天缠一遍因为还要放到眼镜盒里。
  • 吃完晚饭 \(huge\) 进来时看见了 @Charlie_ljk 在掐 @HANGRY_sol 脖子,为了缓解尴尬说了下最近学长志愿报纯计算机科学的越来越少了,现在报电子信息的还多点,以前报网络安全的现在变少了,让我们可以现在关注下国家政策,提前规划未来就业。,举了包括但不限于量子计算机还没有实现普及,国家信息技术开始向硬件发展的例子。
  • 晚休是现班主任和数奥教练查宿,听他们正在讨论我们高一在哪个校区上的问题。
  • 在尝试将眼镜折起来装到眼镜盒里时,发现眼镜腿偏折得更厉害了,不知道还能撑几天。

做题纪要

GHzoj 3752. 音乐播放器

LibreOJ 10185. 「一本通 5.6 例 2」任务安排 2

  • 斜率优化 \(DP\) 板子。

  • \(sumt_{i}=\sum\limits_{j=1}^{i}t_{i},sumc_{i}=\sum\limits_{j=1}^{i}c_{i}\) ,状态转移方程可以写作 \(f_{i}=\min\limits_{j=0}^{i-1} \{ f_{j}+sumt_{i} \times (sumc_{i}-sumc_{j})+s \times (sumc_{n}-sumc_{j}) \}=\min\limits_{j=0}^{i-1} \{ f_{j}-(s+sumt_{i}) \times sumc_{j}+sumt_{i} \times sumc_{i}+s \times sumc_{n} \}\)

  • 删去状态转移方程中的 \(\min\) ,将 \(f_{j}\)\(sumc_{j}\) 看作变量,在以 \(sumc_{j}\) 为横坐标,以 \(f_{j}\) 为纵坐标的平面直角坐标系中可以得到直线 \(y=kx+b\) ,其中 \(\begin{cases} x=sumc_{j} \\ y=f_{j} \\ k=s+sumt_{i} \\ b=f_{i}-sumt_{i} \times sumc_{i}-s \times sumc_{i} \end{cases}\)

  • 此时若要使 \(f_{i}\) 仅可能小,需要使 \(b\) 尽可能小。从小到大枚举每个 \(b\) 的过程等价于 \(y=kx+b\) 的向上平移。而向上平移的过程中遇到的第一个点就是要求的使 \(f_{i}\) 最小的点。

  • 而遇到的第一个点一定是在相邻两点之间斜率单调递增的下凸包上,且只有这个下凸包的顶点才有可能成为最优决策。实际上,对于一条斜率为 \(k\) 的直线,若某个顶点左侧相邻两点斜率都比 \(k\) 小,右侧相邻两点斜率都比 \(k\) 大,则该顶点就是最优决策。

  • 本题中 \(sumc\)\(sumt\) 都是单调递增的,那么新加入的决策点的横坐标 \(x\) 和直线斜率 \(k\) 也是单调递增的。单调队列维护下凸包上相邻两点斜率大于 \(s+sumt_{i}\) 的点即可。新加入决策点时需保证相邻两点斜率递增,即若 \(\dfrac{f_{tail}-f_{tail-1}}{sumc_{tail}-sumc_{tail-1}} \ge \dfrac{f_{i}-f_{tail}}{sumc_{i}-sumc_{tail}}\) 则将队尾弹出。

  • 因分数比较涉及精度和分母可能为 \(0\) 问题,常写作通分后的形式再进行比较。

    点击查看代码
    ll t[10010],c[10010],sumt[10010],sumc[10010],f[10010];
    deque<ll>q;
    int main()
    {
    	ll n,s,i;
    	cin>>n>>s;
    	for(i=1;i<=n;i++)
    	{
    		cin>>t[i]>>c[i];
    		sumt[i]=sumt[i-1]+t[i];
    		sumc[i]=sumc[i-1]+c[i]; 
    	}
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.size()>=2&&f[q[1]]-f[q.front()]<=(s+sumt[i])*(sumc[q[1]]-sumc[q.front()]))//deque 支持随机访问但常数巨大
    		{
    			q.pop_front();
    		}
    		f[i]=f[q.front()]-(s+sumt[i])*sumc[q.front()]+sumt[i]*sumc[i]+s*sumc[n];//至少存在 2 个元素
    		while(q.size()>=2&&(f[q.back()]-f[q[q.size()-2]])*(sumc[i]-sumc[q.back()])>=(f[i]-f[q.back()])*(sumc[q.back()]-sumc[q[q.size()-2]]))
    		//q[0]->q.front()
    		//q[q.size()-1]->q.back()
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

GHzoj 3753. 乒乓球

luogu P5785 [SDOI2012] 任务安排

  • \(t_{i}\) 可能为负数说明 \(sumt_{i}\)\(k\) 不再单调递增。那么单调队列就不能只维护下凸包上相邻两点斜率大于 \(s+sumt_{i}\) 的点,而是维护整个下凸包。转移时二分查找这个顶点即可。

  • \(c_{i}\) 可能为 \(0\) 说明可能不存在斜率。

    点击查看代码
    ll t[300010],c[300010],sumt[300010],sumc[300010],f[300010];
    deque<ll>q;
    bool check(ll mid,ll k)
    {
    	return f[q[mid+1]]-f[q[mid]]<=k*(sumc[q[mid+1]]-sumc[q[mid]]);
    }
    ll divide_search(ll k)
    {
    	ll l=0,r=q.size()-1,mid,ans=0;
    	while(l<r)
    	{
    		mid=(l+r)/2;
    		if(check(mid,k)==true)//mid 这个点应该被弹出
    		{
    			ans=mid+1;
    			l=mid+1;
    		}
    		else
    		{
    			r=mid;
    		}
    	}
    	return q[ans];
    }
    int main()
    {
    	ll n,s,pos,i;
    	cin>>n>>s;
    	for(i=1;i<=n;i++)
    	{
    		cin>>t[i]>>c[i];
    		sumt[i]=sumt[i-1]+t[i];
    		sumc[i]=sumc[i-1]+c[i];
    	}
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		pos=divide_search(s+sumt[i]);
    		f[i]=f[pos]-(s+sumt[i])*sumc[pos]+sumt[i]*sumc[i]+s*sumc[n];
    		while(q.size()>=2&&(f[q.back()]-f[q[q.size()-2]])*(sumc[i]-sumc[q.back()])>=(f[i]-f[q.back()])*(sumc[q.back()]-sumc[q[q.size()-2]]))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

7.10

闲话

  • 去吃早饭的路上,因为“慢悠悠”着走,加上碰到了校长及校长团的人日常视察,我们被校长拦了下来,问我们为啥这么早就下来吃饭,我们说我们教练平常就让我们这个时候来吃饭。旁边还有人(校长团的人)跟我们说这是我们 HZ 的校长,但这人以为我们士气大比拼的时候是没见到他吗。又问我们教练是谁,是 \(field\) 吗,我们说是 \(miaomiao\) ,幸好 \(miaomiao\) 也要去吃饭,然后来帮我们解围,校长问他为什么让我们这么早就下来吃饭,知不知道会影响新高二、高三上课,他说新高一群里统一安排的(声音有点小),校长仍坚持要我们晚会儿吃饭, \(miaomiao\) 顺着说让我们跟着高二的一起吃,旁边的一位副校长说毕竟这些都是他们的学生,早点吃也行,而且错峰吃饭也好,然后就改成了 \(6:55\) 吃饭。接着那位副校长问我们早上跑不跑操, \(miaomiao\) 说现在暂时不跑,等他跟现班主任说了后让现班主任安排(声音逐渐大了起来)。然后 \(miaomiao\) 就让我们先去吃饭了。

  • 上午 \(8:00 \sim 12:00\) \(miaomiao\) 安排了一场模拟赛。

  • \(miaomiao\) 拿来了一个电脑显示屏竖起来安在了他的电脑旁,使其与在 HS 的时候(同时看三个显示屏)一样。

  • 原本以为教练们除了组模拟赛和讲课啥事不干,但 \(miaomiao\) 来找 @K8He 问题解中的题面描述让我打消了这个想法。

  • 用胶带给眼镜做了个固定的套,方便拆卸。

  • 午休现班主任和 \(miaomiao\) 查宿。颓《平凡的世界》。

  • 下午仍是和迪茵公学一起线上讲题,这次因合成了一场比赛所以让首切的讲了。

  • 吃完晚饭后 \(miaomiao\) 跟我们说他问了很多教练,没有一个是像我们这样经常讨论(闲聊)的。

  • 晚上教练捣鼓新 \(OJ\) 时不知道咋把编程、信奥、家国情怀联系起来的,把编程和信奥联系起来感觉会让别人以为是骗钱的,没什么真正实力。第二句话上次还是在 \(bobo\) 朋友圈里的 假期不息,训练不止。

  • 晚休 \(huge\)\(miaomiao\) 查宿;生奥的去外校集训了;小孩哥们入住了隔壁几个宿舍,还有临时班主任管理,一看就是信奥的。

做题纪要

CF311B Cats Transport

  • 对于第 \(i\) 只猫设 \(a_{i}=t_{i}-\sum\limits_{j=1}^{h_{i}}d_{j}\) ,若这只猫想被饲养员接到,饲养员就必须在 \(a_{i}\) 时刻之后从 \(1\) 号山出发。

  • \(a\) 升序排序。从贪心的角度分析,每个饲养员带走的猫一定是按照 \(a\) 升序排序后连续的若干只。

  • \(sum_{i}=\sum\limits_{j=1}^{i}a_{j}\)

  • \(f_{i,j}\) 表示前 \(i\) 个饲养员带走前 \(j\) 只猫,猫等待时间的最小总和。状态转移方程为 \(f_{i,j}=\min\limits_{h=0}^{j-1} \{ f_{i-1,h}+\sum\limits_{l=h+1}^{j}(a_{j}-a_{h}) \}=\min\limits_{h=0}^{j-1} \{ f_{i-1,h}+a_{j} \times (j-h)-(sum_{j}-sum_{h}) \}\) ,边界为 \(f_{1,j}=a_{j} \times j-sum_{j} (j \in [1,m])\)

  • 去掉 \(\min\) ,有 \(\begin{cases} x=h \\ y=f_{i-1,h}+sum_{h} \\ k=a_{j} \\ b=f_{i,j}-a_{j} \times j+sum_{j} \end{cases}\) ,其中横坐标 \(x\) 和直线斜率 \(k\) 是单调递增的。

  • 最终,有 \(\min\limits_{i=1}^{p} \{ f_{i,m} \}\) 即为所求。

    点击查看代码
    ll d[100010],h[100010],t[100010],a[100010],sum[100010],f[110][100010];
    deque<ll>q;
    ll x(ll h)
    {
    	return h;
    }
    ll y(ll i,ll h)
    {
    	return f[i-1][h]+sum[h];
    }
    int main()
    {
    	ll n,m,p,ans=0x7f7f7f7f7f7f7f7f,i,j;
    	cin>>n>>m>>p;
    	for(i=2;i<=n;i++)
    	{
    		cin>>d[i];
    		sum[i]=sum[i-1]+d[i];
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>h[i]>>t[i];
    		a[i]=t[i]-sum[h[i]];
    	}
    	sort(a+1,a+1+m);
    	for(i=1;i<=m;i++)
    	{
    		sum[i]=sum[i-1]+a[i];
    	}
    	for(i=1;i<=m;i++)
    	{
    		f[1][i]=a[i]*i-sum[i];
    	}
    	for(i=2;i<=p;i++)
    	{
    		q.clear();
    		q.push_back(0);
    		for(j=1;j<=m;j++)
    		{
    			while(q.size()>=2&&(y(i,q[1])-y(i,q.front()))<=a[j]*(x(q[1])-x(q.front())))
    			{
    				q.pop_front();
    			}
    			f[i][j]=f[i-1][q.front()]+a[j]*(j-q.front())-(sum[j]-sum[q.front()]);
    			while(q.size()>=2&&(y(i,q.back())-y(i,q[q.size()-2]))*(x(j)-x(q.back()))>=((y(i,j)-y(i,q.back()))*(x(q.back())-x(q[q.size()-2]))))
    			{
    				q.pop_back();
    			}
    			q.push_back(j);
    		}
    	}
    	for(i=1;i<=p;i++)
    	{
    		ans=min(ans,f[i][m]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

GHzoj 3731. 酸碱度中和

luogu P3195 [HNOI2008] 玩具装箱

  • \(sum_{i}=\sum\limits_{j=1}^{i}c_{j}\)

  • \(f_{i}\) 表示把前 \(i\) 个玩具分成若干段放到容器里的最小费用,状态转移方程为 \(f_{i}=\min\limits_{j=0}^{i-1} \{ f_{j}+(i-j-1+sum_{i}-sum_{j}-l)^{2} \}\) ,边界为 \(f_{0}=0\)

  • 去掉 \(\min\) ,有 \(\begin{cases} x=j+sum_{j} \\ y=f_{j}+(j+sum_{j})^{2} \\ k=2(i+sum_{i}-l-1) \\ b=f_{i}-(i+sum_{i}-l-1)^{2} \end{cases}\) ,其中横坐标 \(x\) 是单调不降的,斜率 \(k\) 是单调递增的。

  • 最终,有 \(f_{n}\) 即为所求。

    点击查看代码
    ll c[50010],sum[50010],f[50010];
    deque<ll>q;
    ll x(ll j)
    {
    	return j+sum[j];
    }
    ll y(ll j)
    {
    	return f[j]+(j+sum[j])*(j+sum[j]);
    }
    int main()
    {
    	ll n,l,i;
    	cin>>n>>l;
    	for(i=1;i<=n;i++)
    	{
    		cin>>c[i];
    		sum[i]=sum[i-1]+c[i];
    	}
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.size()>=2&&y(q[1])-y(q.front())<=2*(i+sum[i]-l-1)*(x(q[1])-x(q.front())))
    		{
    			q.pop_front();
    		}
    		f[i]=f[q.front()]+(i-q.front()-1+sum[i]-sum[q.front()]-l)*(i-q.front()-1+sum[i]-sum[q.front()]-l);
    		while(q.size()>=2&&(y(q.back())-y(q[q.size()-2]))*(x(i)-x(q.back()))>=(y(i)-y(q.back()))*(x(q.back())-x(q[q.size()-2])))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

luogu P2120 [ZJOI2007] 仓库建设

  • 因变量重名,用 \(\{ d \}\) 代替原题中的 \(\{ x \}\)

  • \(sum_{i}=\sum\limits_{j=1}^{i}p_{j},mul_{i}=\sum\limits_{j=1}^{i}p_{j}d_{j}\)

  • \(f_{i}\) 表示前 \(i\) 个工厂修建若干仓库的最小总费用,状态转移方程为 \(f_{i}=(sum_{i} \ne 0) \times \min\limits_{j=0}^{i-1} \{ f_{j}+\sum\limits_{k=j+1}^{i-1}p_{k}(d_{i}-d_{k})+c_{i} \}=(sum_{i} \ne 0) \times \min\limits_{j=0}^{i-1} \{ f_{j}+d_{i}(sum_{i-1}-sum_{j})-(mul_{i-1}-mul_{j})+c_{i} \}\) ,边界为 \(f_{0}=0\)

    • 加上 \(sum_{i} \ne 0\) 是为了保证没有无用仓库。

      点击查看 hack 数据
      in:
      1
      0 0 1
      
      ans:
      0
      
      
  • 去掉 \((sum_{i} \ne 0)\)\(\min\) ,有 \(\begin{cases} x=sum_{j} \\ y=f_{j}+mul_{j} \\ k=d_{i} \\ b=f_{i}-d_{i}sum_{i-1}+mul_{i-1}-c_{i} \end{cases}\) ,其中横坐标 \(x\) 和斜率 \(k\) 是单调递增的。

    点击查看代码
    ll d[1000010],p[1000010],c[1000010],sum[1000010],mul[1000010],f[1000010];
    deque<ll>q;
    ll x(ll j)
    {
    	return sum[j];
    }
    ll y(ll j)
    {
    	return f[j]+mul[j];
    }
    int main()
    {
    	ll n,ans=0x7f7f7f7f7f7f7f7f,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>d[i]>>p[i]>>c[i];
    		sum[i]=sum[i-1]+p[i];
    		mul[i]=mul[i-1]+p[i]*d[i];
    	}
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.size()>=2&&y(q[1])-y(q.front())<=d[i]*(x(q[1])-x(q.front())))
    		{
    			q.pop_front();
    		}
    		f[i]=(sum[i]!=0)*(f[q.front()]+d[i]*(sum[i-1]-sum[q.front()])-(mul[i-1]-mul[q.front()])+c[i]);
    		while(q.size()>=2&&(y(q.back())-y(q[q.size()-2]))*(x(i)-x(q.back()))>=(y(i)-y(q.back()))*(x(q.back())-x(q[q.size()-2])))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	for(i=n;i>=1;i--)//不一定在第 n 个工厂建仓库
    	{
    		ans=min(ans,f[i]);//建设仓库的费用可能会抵消运输的费用
    		if(p[i]!=0)
    		{
    			break;
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P3628 [APIO2010] 特别行动队

  • 因变量重名,用 \(\{ d \}\) 代替原题中的 \(\{ x \}\)

  • \(sum_{i}=\sum\limits_{j=1}^{i}x_{j}\)

  • \(f_{i}\) 表示前 \(i\) 名士兵组成若干特别行动队的修正后战斗力总和的最大值。状态转移方程为 \(f_{i}=\max\limits_{j=0}^{i-1} \{ f_{j}+a(sum_{i}-sum_{j})^{2}+b(sum_{i}-sum_{j})+c \}\) ,边界为 \(f_{0}=0\)

  • 去掉 \(\max\) ,有 \(\begin{cases} x=sum_{j} \\ y=f_{j}+a \times sum_{j}^{2} \\ k=(2a \times sum_{i}+b) \\ b'=f_{i}-a \times sum_{i}^{2}-b \times sum_{i}-c \end{cases}\) ,其中横坐标 \(x\) 是单调递增的,斜率 \(k\) 是单调递减的。

  • 单调队列维护上凸包上相邻两点斜率 \(<k\) 的部分即可。

  • 最终,有 \(f_{n}\) 即为所求。

    点击查看代码
    ll d[1000010],sum[1000010],f[1000010];
    deque<ll>q;
    ll x(ll j)
    {
    	return sum[j];
    }
    ll y(ll j,ll a)
    {
    	return f[j]+a*sum[j]*sum[j];
    }
    int main()
    {
    	ll n,a,b,c,i;
    	cin>>n>>a>>b>>c;
    	for(i=1;i<=n;i++)
    	{
    		cin>>d[i];
    		sum[i]=sum[i-1]+d[i];
    	}
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.size()>=2&&y(q[1],a)-y(q.front(),a)>=(2*a*sum[i]+b)*(x(q[1])-x(q.front())))
    		{
    			q.pop_front();
    		}
    		f[i]=f[q.front()]+a*(sum[i]-sum[q.front()])*(sum[i]-sum[q.front()])+b*(sum[i]-sum[q.front()])+c;
    		while(q.size()>=2&&(y(q.back(),a)-y(q[q.size()-2],a))*(x(i)-x(q.back()))<=(y(i,a)-y(q.back(),a))*(x(q.back())-x(q[q.size()-2])))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

GHzoj 3734. 公路

GHzoj 3732. 聪明的小明

GHzoj 3733. 线段树

AT_dp_z Frog 3

  • \(f_{i}\) 表示跳到编号为 \(i\) 的石头的最小花费,状态转移方程为 \(f_{i}=\min\limits_{j=1}^{i-1} \{ f_{j}+(h_{j}-h_{i})^{2}+c \}\) ,边界为 \(f_{1}=0\)

  • 去掉 \(\min\) ,有 \(\begin{cases} x=h_{j} \\ y=f_{j}+h_{j}^{2} \\ k=2h_{i} \\ b=f_{i}-h_{i}^{2}+c \end{cases}\) ,其中横坐标 \(x\) 和斜率 \(k\) 是单调递增的。

    点击查看代码
    ll h[200010],f[200010];
    deque<ll>q;
    ll x(ll j)
    {
    	return h[j];
    }
    ll y(ll j)
    {
    	return f[j]+h[j]*h[j];
    }
    int main()
    {
    	ll n,c,i;
    	cin>>n>>c;
    	for(i=1;i<=n;i++)
    	{
    		cin>>h[i];
    	}
    	f[1]=0;
    	q.push_back(1);
    	for(i=2;i<=n;i++)
    	{
    		while(q.size()>=2&&y(q[1])-y(q.front())<=2*h[i]*(x(q[1])-x(q.front())))
    		{
    			q.pop_front();
    		}
    		f[i]=f[q.front()]+(h[q.front()]-h[i])*(h[q.front()]-h[i])+c;
    		while(q.size()>=2&&(y(q.back())-y(q[q.size()-2]))*(x(i)-x(q.back()))>=(y(i)-y(q.back()))*(x(q.back())-x(q[q.size()-2])))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    

luogu P2900 [USACO08MAR] Land Acquisition G

  • 多倍经验: SP15086 ACQUIRE - Land Acquisition

  • 本题中购买的土地不一定是连续的。

  • 将土地按长升序,宽降序排序,同时删去能免费买的土地。

  • 设此时土地为 \(n\)\(f_{i}\) 表示买前 \(i\) 块土地的最小费用,状态转移方程为 \(f_{i}=\min\limits_{j=0}^{i-1} \{ f_{j}+l_{j+1}w_{i} \}\) ,边界为 \(f_{0}=0\)

  • 去掉 \(\min\) ,有 \(\begin{cases} x=-l_{j+1} \\ y=f_{j} \\ k=w_{i} \\ b=-f_{i} \end{cases}\) ,其中横坐标 \(x\) 和斜率 \(k\) 是单调递增的。

    点击查看代码
    struct node
    {
    	ll w,l;
    }a[50010],b[50010];
    ll f[50010];
    deque<ll>q;
    bool cmp(node a,node b)
    {
    	return (a.w==b.w)?(a.l<b.l):(a.w<b.w);
    }
    ll x(ll j)
    {
    	return -a[j+1].l;
    }
    ll y(ll j)
    {
    	return f[j];
    }
    int main()
    {
    	ll n=0,m,maxx=0,i;
    	cin>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>b[i].w>>b[i].l;
    	}
    	sort(b+1,b+1+m,cmp);
    	for(i=m;i>=1;i--)
    	{
    		if(b[i].l>maxx)
    		{
    			maxx=b[i].l;
    			n++;
    			a[n].w=b[i].w;
    			a[n].l=b[i].l;
    		}
    	}
    	sort(a+1,a+1+n,cmp);
    	f[0]=0;
    	q.push_back(0);
    	for(i=1;i<=n;i++)
    	{
    		while(q.size()>=2&&y(q[1])-y(q.front())<=a[i].w*(x(q[1])-x(q.front())))
    		{
    			q.pop_front();
    		}
    		f[i]=f[q.front()]+a[q.front()+1].l*a[i].w;
    		while(q.size()>=2&&(y(q.back())-y(q[q.size()-2]))*(x(i)-x(q.back()))>=(y(i)-y(q.back()))*(x(q.back())-x(q[q.size()-2])))
    		{
    			q.pop_back();
    		}
    		q.push_back(i);
    	}
    	cout<<f[n]<<endl;
    	return 0;
    }
    
posted @ 2024-07-01 07:47  hzoi_Shadow  阅读(189)  评论(7编辑  收藏  举报
扩大
缩小