NOI Online #1入门组详细题解

Solution

T1

考虑 O ( n 2 ) O(n^2) O(n2)暴力: 枚举 a , b a,b a,b,然后求出对应的 c c c,更新答案。

这种做法会T掉。虽然我们可以将时间复杂度优化到 O ( n ) O(n) O(n),可事实上暴力大力剪枝+卡常就能过。

①对于一个 a a a,我们将 b b b先从 0 − 3 0-3 03枚举一遍,如果这些 b b b都不行,那么这个 a a a就不行了;直接break;

②得到一个 c c c的特解后,每次将 c c c 4 4 4而不是加 1 1 1,这样可以保证对于每一个 c c c,都存在一个自然数 b b b


③把;改成;

④在循环的 i n t int int前加上个 r e g i s t e r register register

⑤不要开 l o n g   l o n g long\ long long long

于是这样不开O2都能过。

T2

裸的超纲整数拆分问题。

首先,我们考虑 d p dp dp d p dp dp有两种做法:

①无限背包(以 1 − n 1-n 1n中每一个数为一个物品,价值均为 1 1 1,背包容量为 n n n且必须装满)
②另外一种 d p dp dp
状态设计: d p i , j : dp_{i,j}: dpi,j: 目前选了 i i i个数,和为 j j j
状态转移: d p i , j = d p i , j − i + d p i − 1 , j − 1 dp_{i,j}=dp_{i,j-i}+dp_{i-1,j-1} dpi,j=dpi,ji+dpi1,j1
这个状态转移式是什么意思呢? d p i , j − i dp_{i,j-i} dpi,ji可以认为是将 d p i , j − i dp_{i,j-i} dpi,ji表示的序列的所有数加 1 1 1 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1表示将 d p i − 1 , j − 1 dp_{i-1,j-1} dpi1,j1所表示的序列最后面多上一个 1 1 1。相当于我们可以将一个序列全局加 1 1 1,或者在最后面添上一个 1 1 1,显然这个序列一直是有序的。

这两种 d p dp dp都是 O ( n 2 ) O(n^2) O(n2)的。它们各有所长——
①假设物品的数量为 x x x,那么复杂度是 O ( x n ) O(xn) O(xn)。即,如果物品数量不多,这个 d p dp dp很不错!
②假设物品最多能有 x x x个,那么复杂度是 O ( x n ) O(xn) O(xn)。即,如果物品最多能有的数量不多,那么这个 d p dp dp很棒!

考虑根号分治。对于所有不大于 n \sqrt n n 的数,我们作第一种 d p dp dp;对于剩余的数做第二种 d p dp dp。可以发现,对于①的物品的数量只有 n \sqrt n n 个,对于②而言,由于所选的所有数都不小于 n \sqrt n n ,所以物品最多只能有 O ( n ) O(\sqrt n) O(n )个。

最后将两个 d p dp dp数组合并起来即可得到答案。时间复杂度 O ( n n ) O(n \sqrt n) O(nn )

根号分治牛逼!

T3

首先,我们发现一个神奇的东西: n ≤ 100 n≤100 n100

所以复杂度中一定有 n 3 n^3 n3这一个部分,但是这个数值很小,所以后面还会再套上一个什么东西,比如 O ( n 3 m ) O(n^3 \sqrt m) O(n3m )之类的。但是, k k k的值似乎非常大!所以,我们是不是要想到……

⌊ \lfloor 矩乘,以 n n n为矩阵的边长,以 t t t为矩阵的指数 ⌉ \rceil

想到这里,我们要考虑如何设计出一个可以用矩阵乘法优化的 d p dp dp d p i , j dp_{i,j} dpi,j表示当前到了第 i i i个节点,一共使用了 j j j次魔法。状态转移显然: d p i , j = min ⁡ 1 ≤ j ≤ n d p i ′ , j − 1 + c o s t ( i ′ , i ) dp_{i,j}=\min_{1≤j≤n} {dp_{i',j-1}+cost(i',i)} dpi,j=1jnmindpi,j1+cost(i,i)

这里 c o s t ( i ′ , i ) cost(i',i) cost(i,i)表示从 i ′ i' i经过一些道路到达 i i i,在这个期间使用恰好一次魔法时的最小代价。现在关键在于如何求出这一个 c o s t ( i ′ , i ) cost(i',i) cost(i,i)

我们枚举在从 i ′ i' i i i i的过程中使用魔法的是哪条边。假设这条有向边是 u − > v u->v u>v,那么此时的花费就是 d i s ( i ′ , u ) − e d g e ( u , v ) + d i s ( v , i ) dis(i',u)-edge(u,v)+dis(v,i) dis(i,u)edge(u,v)+dis(v,i),这里 d i s ( u , v ) dis(u,v) dis(u,v)表示 u u u v v v的最短路径, e d g e ( u , v ) edge(u,v) edge(u,v)表示 u − > v u->v u>v这条边的边权。

于是我们可以在 O ( n 3 ) O(n^3) O(n3)的代价下通过 F l o y d Floyd Floyd求出任意两个节点之间的 d i s dis dis,在 O ( n 2 m ) O(n^2 m) O(n2m)的代价下求出 c o s t cost cost,然后在 O ( n 3 log ⁡ k ) O(n^3 \log k) O(n3logk)的代价下用矩阵乘法来加速这个转移。具体来说,我们快速转移的是一个长度为 n n n的数组,假设当前为第 t t t轮,那么数组中的第 p p p个数就表示 d p p , t dp_{p,t} dpp,t。注意矩阵乘法是广义的,但是同样拥有结合律,从而可以使用快速幂来优化矩乘。

总时间复杂度 O ( n 2 ( m + n log ⁡ k ) O(n^2(m+n\log k) O(n2(m+nlogk)

Summary

一套不错的题目,可是真的不适合普及组。

第一题是一道比较水的数学题,甚至放了剪枝、卡常的小暴力过。
第二题是一道套路的 d p dp dp题,巧用根号分治,结合两种 d p dp dp的优缺点分治,达到平衡,时间复杂度优化到了 O ( n n ) O(n \sqrt n) O(nn )
第三题仍然是一道 d p dp dp题,考验了数据范围观察能力(即经验),还有广义矩阵乘法的巧用以及最短路。

Code

A

#include <bits/stdc++.h>
#define rg register
using namespace std;

int n,maxsumv=-1,maxminv=-1,a,b,c;

int minn(int x,int y,int z)
{
	return min(min(x,y),z);
}

signed main()
{
	cin>>n;
	for (rg int i=0;7*i<=n;i++)
	{
		int k=n-7*i,pos;
		for (rg int j=0;j<=3;j++)
		{
			if ((k-3*j)%4==0&&k-3*j>=0)
			{
				pos=j;
				break;
			}
		}
		for (int j=pos;3*j<=k;j+=4)
		{
			int first=i,second=(k-3*j)/4,third=j;
			if (minn(first,second,third)>maxminv)
				maxminv=minn(first,second,third),maxsumv=first+second+third,a=first,b=second,c=third;
			
			else if (minn(first,second,third)==maxminv)
			{
				if (first+second+third>maxsumv)
				  maxsumv=first+second+third,a=first,b=second,c=third;
			}
		}
	}
	if (a*7+b*4+c*3==n)  cout<<a<<' '<<b<<' '<<c<<endl;	
	else cout<<-1<<endl;
	
	return 0;
}

B

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100005;

int n,p,m;ll ans=0;
int f[100005],g[405][100005];

int getmod(int x)
{
	return x-(x/p)*p;
}

signed main()
{
    cin>>n>>p;
    m=sqrt(n)+1;
    
    f[0]=1;
    for (int i=1;i<m;i++)
	{
        for (int j=i;j<=n;j++)  f[j]=getmod(f[j-i]+f[j]);
    }
    g[0][0]=1;
    for (int i=1;i<m;i++)
    {
    	for (int j=i;j<=n;j++)
    	{
    		g[i][j]=g[i][j-i];
    		if (j>=m)  g[i][j]=getmod(g[i][j]+g[i-1][j-m]);
    	}
    }
    int ans=0;
    for (int i=0;i<=n;i++)
	{
        int sum=0;
        for (int j=0;j<m;j++)  sum=(sum+g[j][n-i])%p;
        
        ans=(ans+1ll*f[i]*sum)%p;
    }
    cout<<ans<<endl;
    
    return 0;
}

C

#include <bits/stdc++.h>
#define int long long
#define cost fjioadf
#define inf 4000000000000000007
using namespace std;

int n,m,kkk;
int GA[105][105],cost[105][105];

struct node{int u,v,w;}a[10005];
struct Matrix
{
	int a[110][110];
	Matrix() {for (int i=0;i<=n;i++)  for (int j=0;j<=n;j++)  a[i][j]=inf;}
}base;

Matrix operator * (const Matrix &x,const Matrix &y)
{
	Matrix res;
	for (int i=0;i<=n;i++)
	{
		for (int j=0;j<=n;j++)
		{
			res.a[i][j]=inf;
			for (int k=0;k<=n;k++)
			  res.a[i][j]=min(res.a[i][j],x.a[i][k]+y.a[k][j]);
		}
	}
	return res;
}

Matrix quick_power(Matrix x,int y)
{
	Matrix res=x;y--;
	for (;y;y=y>>1,x=(x*x))
	{
		if (y&1)  res=(res*x);
	}
	return res;
}

signed main()
{
	cin>>n>>m>>kkk;
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++)
		{
			if (i!=j)  GA[i][j]=inf;
			else GA[i][j]=0;
		}
	}
	for (int i=1;i<=m;i++)
	  cin>>a[i].u>>a[i].v>>a[i].w,GA[a[i].u][a[i].v]=a[i].w;
	
	for (int k=1;k<=n;k++)
	{
		for (int i=1;i<=n;i++)
		{
			for (int j=1;j<=n;j++)
			{
				if (GA[i][j]>GA[i][k]+GA[k][j])
				  GA[i][j]=GA[i][k]+GA[k][j];
			}
		}
	}
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=n;j++)  cost[i][j]=inf;
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=n;j++)
		for (int k=1;k<=m;k++)
		{
			int v=a[k].u;
			int u=a[k].v;
			cost[i][j]=min(cost[i][j],GA[j][v]+GA[u][i]-a[k].w);
		}
	for (int i=0;i<=n;i++)
	{
		for (int j=0;j<=n;j++)
		{
			if (i==0&&j!=0)  base.a[i][j]=inf;
			else if (j==0)
			{
				if (i==0||i==1)  base.a[i][j]=0;
				else base.a[i][j]=inf;
			}
			else base.a[i][j]=cost[n-j+1][n-i+1];
		}
	}
	if (kkk==0)
	{
		cout<<GA[1][n]<<endl;
		return 0;
	}
	Matrix res=quick_power(base,kkk+1);
	cout<<res.a[n][0]<<endl;
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值