11.18 NOIP模拟赛

T1 P2209 [USACO13OPEN] Fuel Economy S


原题链接

这道题就是一道搜索题,也并不是很难。

思路:贪心。首先对各个加油站的坐标从小到大排序,然后进行贪心求解。

1.找在能力范围之内所能到达的比当前加油站的油价小的第一个加油站。如果没有就找当前能力范围内油价最小的加油站。

2.跳到上一步找到的加油站。

3.如果在加满油后,所能到达的加油站的油价有比当前便宜的,那么只要加满足够到达那个加油站的油。

4.反之,就加满油。

c o d e code code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN = 1e6 + 10;
const int INF = 0x7f7f7f7f;
int N, G, B, D;
int ans, now, lost, pos;
struct nond {
	int x, y;
} v[NN];
int cmp(nond a, nond b) {
	return a.x < b.x;
}
signed main() {
	cin >> N >> G >> B >> D;
	for (int i = 1; i <= N; i++)
		cin >> v[i].x >> v[i].y;
	v[0].x = 0;
	v[0].y = INF;
	v[N + 1].x = D;
	v[N + 1].y = 0;
	sort(v + 1, v + 2 + N, cmp);
	if (v[1].x - v[0].x > B) {
		cout << "-1";
		return 0;
	}
	for (int i = 2; i <= N + 1; i++)
		if (v[i].x - v[i - 1].x > G) {
			cout << "-1";
			return 0;
		}
	lost = B;
	now = 0;
	pos = 0;
	while (now != D) {
		int num = pos + 1;
		bool flag = 0, flag1 = 0;;
		for (int i = pos + 1; i <= N + 1; i++)
			if (v[i].y < v[pos].y && v[i].x - now <= lost) {
				num = i;
				flag1 = 1;
				break;
			}
		if (!flag1)
			for (int i = pos + 1; i <= N + 1; i++)
				if (v[i].y <= v[num].y && v[i].x - now <= lost)
					num = i;
		lost -= v[num].x - now;
		now = v[num].x;
		pos = num;
		for (int i = pos + 1; i <= N + 1; i++)
			if (v[i].y < v[pos].y && v[i].x - now <= G) {
				if (v[i].x - now - lost <= 0)
					ans = ans;
				else {
					ans += (v[i].x - now - lost) * v[pos].y;
					lost += v[i].x - now - lost;
				}
				flag = 1;
				break;
			} else if (v[i].x - now > G) break;
		if (!flag) {
			ans += (G - lost) * v[pos].y;
			lost = G;
		}
	}
	cout << ans;
	return 0;
}

T2 P2303 [SDOI2012] Longge 的问题


原题链接

这就是一道纯数学题。

如果不化简,直接就是按照题意打最简单的暴力的话,在原题会有 65 65 65大分。

暴力做法 65 p t s 65pts 65pts(不用动脑)

#include<bits/stdc++.h>
#define int long long 
using namespace std;
//const int mod=1e9+7;人机
int n;
int ans=0;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int cnt;
		cnt=__gcd(i,n);
		ans+=cnt;
	}
	cout<<ans;
	return 0;
}

现在来想想正解。

先将式子转化一下:

 原式  = ∑ i = 1 n gcd ⁡ ( i , n ) = ∑ d ∣ n d × ∑ i = 1 n [ gcd ⁡ ( i , n ) = d ] = ∑ d ∣ n d × ∑ i = 1 n d [ gcd ⁡ ( i , n d ) = 1 ] = ∑ d ∣ n d ⋅ φ ( n d ) \begin{aligned} \text { 原式 } & =\sum_{i=1}^{n} \operatorname{gcd}(i, n) \\ & =\sum_{d \mid n} d \times \sum_{i=1}^{n}[\operatorname{gcd}(i, n)=d] \\ & =\sum_{d \mid n} d \times \sum_{i=1}^{\frac{n}{d}}\left[\operatorname{gcd}\left(i, \frac{n}{d}\right)=1\right] \\ & =\sum_{d \mid n} d \cdot \varphi\left(\frac{n}{d}\right) \end{aligned}  原式 =i=1ngcd(i,n)=dnd×i=1n[gcd(i,n)=d]=dnd×i=1dn[gcd(i,dn)=1]=dndφ(dn)

转化思路很套路,我们只需要暴力枚举 n n n的因数并快速求出单个 φ \varphi φ函数的值即可。

c o d e code code

#include<bits/stdc++.h>
using namespace std;
const int N=(1<<16)+5;
int n,tot,p[N];
bool flg[N];
void sieve(int n) {
	for(int i=2;i<=n;++i) {
		if(!flg[i]) p[++tot]=i;
		for(int j=1;j<=tot&&i*p[j]<=n;++j) {
			flg[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
long long phi(long long x) {
	long long ans=x;
	for(int i=1;i<=tot&&1LL*p[i]*p[i]<=x;++i) {
		if(x%p[i]) continue;
		ans=ans/p[i]*(p[i]-1);
		while(x%p[i]==0) x/=p[i];
	}
	if(x>1) ans=ans/x*(x-1);
	return ans;
}
int main() {
	long long n,ans=0;
	scanf("%lld",&n);
	sieve((int)sqrt(n));
	for(long long i=1;i*i<=n;++i) {
		if(n%i==0) {
			ans+=i*phi(n/i);
			if(i*i!=n) ans+=n/i*phi(i);
		}
	}
	printf("%lld\n",ans);
	return 0;
}

T3 Bear and Tree Jumps


原题链接

这是一道换根dp题。

先想一下思路:

换根dp,尝试两次转移来求解答案——对于树上的所有点 ( u , v ) (u,v) (u,v), f ( u , v ) f(u,v) f(u,v)的总和。

我们知道换根dp需要进行两次转移,我们分别来看两种转移。

第一次转移:

尝试得到以 x x x为根时,节点 x x x x x x子树上所有点的步数和。如果我们得到节点 v v v所在子树的信息,在向其父亲节点 x x x传递贡献时, v v v子树上的距离 v v v k k k的倍数的点的贡献会整体 + 1 +1 +1(也就是有多少个点到 v v v的距离模 k = 0 k=0 k=0,贡献就新增多少),所以还要把距离和 k k k的关系的点的数目记录下来。

我们设 d p [ x ] dp[x] dp[x]表示 x x x子树上所有点到 x x x的贡献。

s u m [ x ] [ y ] sum[x][y] sum[x][y]表示在 x x x子树上到点 x x x的距离模 k k k后为 y y y的点的数量,用来辅助转移。

void dfs(int x,int fa)
{
	sum[x][0]=1;//注意这里,自己到自己距离为0
	for(int &v:G[x])
	{
		if(v==fa)
			continue;
		dfs(v,x);
		dp[x]+=dp[v];//回溯将v的贡献传递给其父亲
		for(int i=0;i<k;i++)//点的距离全部变为(i+1)%k
			sum[x][i]+=sum[v][(i-1+k)%k];
		dp[x]+=sum[v][0];//0距离再增加会多走一步,贡献全部+1
	}
}
第二次转移

d p 2 [ x ] dp2[x] dp2[x]表示以 x x x为根节点时,整棵树上所有点到达 x x x时所需要的步数和。

r e t [ x ] [ y ] ret[x][y] ret[x][y]表示以 x x x为根时树上到点 x x x的距离模 k k k后等于 y y y的点的数量,用来辅助第二次转移。

使用类似容斥的思路,先假设树上所有点到 v v v的距离相对于原来到 x x x的距离全部增加 1 1 1,我们发现 v v v子树上的距离会被错误的增加, v v v子树上到 v v v的距离相对于到 x x x点不应增加反而应该减少,那么我们删掉这一部分的数量并且使用 s u m sum sum里的信息来修正。

ret[v][i]=ret[x][(i-1+k)%k];//距离全部+1,子树是错误的,下面两行修正
ret[v][i]-=sum[v][(i-2+k)%k];//虚假的子树
ret[v][i]+=sum[v][i];//真正的子树

接下来就是换根过程。

dp2[x]=dp2[fa];
dp2[x]+=ret[fa][0]-sum[x][(k-1+k)%k];//x补树上到fa距离为0的点贡献+1
dp2[x]-=sum[x][0];//x子树上所有到x为0的点贡献-1

c o d e code code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
#define debug(x) std:: cerr << #x << " = " << x << std::endl;
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int sum[maxn][6],dp[maxn],dp2[maxn],k,ans=0;
vector<int>G[maxn];
void dfs(int x,int fa)
{
	sum[x][0]=1;
	for(int &v:G[x])
	{
		if(v==fa)
			continue;
		dfs(v,x);
		dp[x]+=dp[v];
		for(int i=0;i<k;i++)//0再向上会突破一级
			sum[x][i]+=sum[v][(i-1+k)%k];
		dp[x]+=sum[v][0];//步数全部+1
	}
}
int ret[maxn][6];//表示到x点模数为y的点的个数
void dfs2(int x,int fa)
{//转移下来时,x补树上距离为0的点贡献+1
	if(x!=1)
	{
		dp2[x]=dp2[fa];
		dp2[x]+=ret[fa][0]-sum[x][(k-1+k)%k];//x补树上到fa距离为0的点贡献+1
		dp2[x]-=sum[x][0];//x子树上所有到x为0的点贡献-1
	}
	ans+=dp2[x];
	for(int &v:G[x])
	{
		if(v==fa)
			continue;
		for(int i=0;i<k;i++)
		{//先假设上一层所有点距离增大,0->1
			ret[v][i]=ret[x][(i-1+k)%k];//子树是错误的,下面两行修正
			ret[v][i]-=sum[v][(i-2+k)%k];//虚假的子树,
			ret[v][i]+=sum[v][i];//真正的子树
		}
		dfs2(v,x);
	}
}
signed main(signed argc, char const *argv[])
{
	int n,u,v;
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	for(int i=0;i<k;i++)
		ret[1][i]=sum[1][i];
	dp2[1]=dp[1];
	dfs2(1,1);
	cout<<ans/2<<endl;
	return 0;
}
/*
 *可以从u点一次跳到任何与当前节点不超过k的节点
 *f(u,v)为u->v最少次数,求对所有点对的f总和
 *换根dp,以根节点深度为0dfs
 *k很小的时候才容易转移,记录对k取模后的余数
 *dp[x][k]表示在以x为根的子树上距离为k的节点的步数和
*/

T4 P3545 [POI2012] HUR-Warehouse Store


原题链接

贪心做法:

这道题用贪心求解即可,对于每一个顾客:
(1)我们能满足他,满足他,并将这位顾客所要的数量存起来;

(2)我们不能满足他,就看看我们之前满足的人,若是不卖给其中要的物品数量最大的人,我们就能满足他,那么我们就不卖给那个人,传而卖给现在这个人,并将现在这个人存起来(因为这样可以省下更多的钱去满足其他顾客的需求,而满足顾客需求的人数不变)。那么显然用二叉堆存储就可以了。

STL大法好:

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 250010;
int n, a[N];
priority_queue<PII> q;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++ ) {
        cin >> a[i];
    }
    long long s = 0;
    for (int i = 1; i <= n; i ++ ) {
        s += a[i];
        int x;
        cin >> x;
        if (s >= x) {
            s -= x;
            q.push({x, i});   //如果能满足,就满足
        }
        else {
            if (q.size()) {    
                if (q.top().x > x) {
                    s += q.top().x;
                    q.pop();
                    s -= x;
                    q.push({x, i});
                }
            }
        }
    }
    cout << q.size() << endl;
    int x = q.size();
    for (int i = 1; i <= x; i ++ ) {
        cout << q.top().y << " ";
        q.pop();
    }
    return 0;
}

T5 P1453 城市环路

原题链接

经典图论问题

因为如果一条边的端点已经全部出现的话,我们把这条边加进去,就构成了一个环,

所以开一个 f l a g [ ] flag[] flag[]标记,标记这个点有没有输入过,

如果 KaTeX parse error: Expected 'EOF', got '&' at position 8: flag[u]&̲&flag[v],那么这肯定是一个环上的一条边,然后让 A = u , B = v , A=u,B=v, A=u,B=v,并且我们不把这条边加进图里,这样就构成了一棵树

A , B A,B A,B做两次树形DP,找最大值。 和没有上司的舞会那道题一样

还有就是要取 m a x ( d p [ A ] [ 0 ] , d p [ B ] [ 0 ] ) max(dp[A][0],dp[B][0]) max(dp[A][0],dp[B][0])

为什么是不选A和B的情况的最大值:

因为我们如果是在选A或B的情况中取最大值,我们不能保证A(B)选了,但是B(A)没选

c o d e code code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, p[N], fa[N], S, T;
struct ed {
	int to, next;
} e[N << 1];
int cnt, head[N];
double f[N][2];
void add(int u, int v) {
	e[++cnt] = (ed) {
		v, head[u]
	};
	head[u] = cnt;
}
int find(int x) {
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void dfs(int u, int fa) {
	f[u][1] = p[u];
	f[u][0] = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == fa)continue;
		dfs(v, u);
		f[u][0] += max(f[v][0], f[v][1]);
		f[u][1] += f[v][0];
	}
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++){
		 cin >> p[i], fa[i] = i;
	}
	for (int i = 1; i <= n; i++) {
		int u, v;
		cin >> u >> v;
		u++, v++;
		if (find(u) == find(v)) {
			S = u, T = v;
			continue;
		}
		add(u, v);
		add(v, u);
		fa[find(v)] = find(u);
	}
	double k, ans = 0;
	scanf("%lf", &k);
	dfs(S, 0);
	ans = f[S][0];
	dfs(T, 0);
	ans = max(ans, f[T][0]);
	printf("%.1lf\n", ans * k);
	return 0;
}

总结:

数学式子偏多,但是转化起来有点困难,换根dp很少用,所以考试可能会想不起来。还是贪心,搜索,dp之类的题难想,但代码较好写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值