[SCOI2012]滑雪 题解

[SCOI2012]滑雪

问:景点数最大的同时 滑行总距离最小

景点数最大 即求 最大连通块

滑行总距离最小 即求 最小生成树

对于多条件问题 有限考虑硬性条件(可行性) 再考虑最优性

1.可行性

滑行必须从高到低(相等也可),即更新节点时必然是从高的点更新低的点,

所以选择节点时 优 先 考虑高的节点

结论 对所有节点从高到低排序

2.最优性

与最小生成树同理

优先选择长度最短的边

结论 对所有边从小到大排序

3.坑点

  1. 当两个点高度相等是,是双向边
  2. 既然是双向边,就要开两倍数组,否则RE
  3. 只考虑了最优性而没有考虑可行性

4.代码如下

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
const ll N = 1e5+7, M = 2 * 1e6+7, INF = 5e9+9; // 可能存在双向边
ll n,m,h[N];
ll head[N],tot;
struct edge
{
	ll ver,dis,nex;
}e[M],pe[M];
void add(ll u,ll v,ll w)
{
	e[++tot].nex = head[u];
	e[tot].ver = v;
	e[tot].dis = w;
	head[u] = tot;
}
ll phead[N],ptot;
void padd(ll u,ll v,ll w)
{
	pe[++ptot].nex = phead[u];
	pe[ptot].ver = v;
	pe[ptot].dis = w;
	phead[u] = ptot;
}
bool arr[N];
ll num;
void bfs(ll begin)
{
	queue<ll> q;
	arr[begin] = 1;
	num++;
	q.push(begin);
	
	while(q.size())
	{
		ll now = q.front();
		q.pop();
		for(ll i=head[now];i;i=e[i].nex)
		{
			padd(now,e[i].ver,e[i].dis);
			if(arr[e[i].ver])continue;
			arr[e[i].ver] = 1;
			num++;
			q.push(e[i].ver);
		}
	}
}
struct cmp
{
	bool operator()(edge a,edge b)
	{
		if(h[a.ver]!=h[b.ver])return h[a.ver] < h[b.ver];//可行性
		return a.dis>b.dis;//最优性
	}
};
ll dis[N],sum;
void prim(ll begin)
{
	priority_queue< edge , vector<edge> , cmp > q;
	for(ll i=1;i<=n;i++)dis[i]=INF;
	dis[begin] = 0;
	for(ll i=phead[begin];i;i=pe[i].nex)
	{
		if(dis[begin] + pe[i].dis < dis[pe[i].ver])
		{
			q.push(pe[i]);
			dis[pe[i].ver]=pe[i].dis;
		}
	}
	while(q.size())
	{
		edge now = q.top();
		q.pop();
		if(!dis[now.ver])continue;
		dis[now.ver]=0;
		sum+=now.dis;
		for(ll i=phead[now.ver];i;i=pe[i].nex)
		{
			if(dis[begin] + pe[i].dis < dis[pe[i].ver])
			{
				dis[pe[i].ver] = pe[i].dis;
				q.push(pe[i]);
			}
		}
	}
}
int main()
{
	n = read();
	m = read();
	for(ll i=1;i<=n;i++)h[i]=read();
	for(ll i=1;i<=m;i++)
	{
		ll u,v,w;
		u=read();
		v=read();
		w=read();
		if(h[u]<=h[v])add(v,u,w);
		if(h[u]>=h[v])add(u,v,w);
	}
	bfs(1);//事实上不需要BFS
	prim(1);
	printf("%lld %lld",num,sum);
	return 0;
}

TG2011总结

TG2011真题


T1 铺地毯

我们只关心最后一张覆盖所求点矩形的编号,倒序查找,符合条件直接输出就好


T2 计算系数

很EZ,杨辉三角,之前周测写过 0912限时测试


T3 选择客栈

之前周测也写过 0912限时测试

前缀和维护前i个点中有几个 最低消费不超过 p 元的咖啡店 ,即 p > bi

然后O(n2) 求 两个颜色相同的点之间有 满足p>b的点 的方案数 , 80分 ,O2优化 100分


T4 聪明的质监员

30分做法:

我们可以暴力的枚举W , 然后对于每个W , O(nm)求 最小的 $\sum_{j=1}^{m}y_i$

可以考虑对30分做法进行优化

枚举 ---> 二分 70分

观察式子,可以发现,当W增大,满足要求的矿石减少,导致 yi 减少,反之同理。

所以我们可以二分 W , 取最小的 $\sum_{j=1}^{m}y_i$

前缀和 100分

对于每个W , 用前缀和先处理出 $\sum_{j=l_i}^{r^{i}}[w_j{\geq}W]$ 和 , $\sum_{j=l_i}^{r^{i}}[w_j {\geq} W]v_j$ 就不用逐个枚举浪费时间


T5 观光公交

考虑部分分

对于10% 的数据,k=0

记录汽车 到达每个景点的时间S[i] 和 离开每个景点的时间F[i]

S[i] = F[i-1] + d[i]

F[i] = max(F[i-1]-d[i],F[i])

对于20%的数据,k=1

枚举每一个d[i],使其减一,每次跑一遍10分的做法


100+100+80+30+30=340>270

1128模拟总结

1128模拟总结

可拿分数 100+100+60+40=300

实际得分 100+ 40+10 + 40=190


T1

problem:

T次询问,每次询问判断一个数是不是某两个斐 波那契数的乘积

对于 100%的数据,1≤T≤100,0≤A≤1e9。

solution:

预处理出前40个 斐波那契数 ,n2 处理出乘积,然后O(1)查询


T2

problem:带有边权的最优贸易

solution:加超级源点,边权设为买入时的价格,跑一遍dijkstra;


T3

problem:一个n*m的数表,给定两个大小一样、不相交且没有共同的边界的矩形,并交换两个矩形内的内容。

暴力模拟swap 60分

模拟时写了个奇怪的特判,WA声一片。删掉有60分。


T4

problem:从1到N中选出不超过K个正整数,求使得这些数的乘积是一个无平方因子数的方案数

暴力深搜 40分

对于每一个数只有选或不选,判断乘积是否为无平方因子数,ans++

题解 P5092 【[USACO04OPEN]Cube Stacking】

题目传送门

看完题面先写了一份暴力

然后竟然AC了

直接模拟

刚开始有30000个小方块

每次移动 都是把一个方块移动到另一个方块上变成一个大方块

可以发现每次移动大方块内的小方块都是先进先出,于是对于每一个方块都可以用队列维护

当我们把 一个小方块x 移动到另一方块y上时,x 下方的方块数目等于移动前 y的所含的方块数目

用d[i]表示方块i下方的方块数目,于是我们可以O(1)查询

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int N = 30007;
queue<int> num[N]; // 队列维护 每个大方块里 的小方块 
ll v[N],cnt[N],d[N],n;// v[i] 小方块i在哪个大方块中 , cnt[i] 大方块i有多少个小方块 , d[i] 小方块i下方有多少个小方块 
void M(ll x,ll y)
{   
    ll a=v[x],b=v[y];//先记录 x,y 所在的 大方块编号 
    for(ll i=1;i<=cnt[a];++i)
    {   
		//将 x 移入 y 所在的方块  
        ++cnt[b];
        ll now = num[a].front();
        num[b].push(now);
        v[now]=b; 
        num[a].pop();
        // 每次移动都更新 d[i] 
		// now下方的方块数目等于移动前 b的所含的方块数目 
        d[now]=cnt[b]-1; 
    }
}
int main()
{
    n = read();
    for(ll i=1;i<=30000;++i)
    {
		//刚开始对每个小方块 初始化 
        num[i].push(i);
		v[i]=i;
		cnt[i]=1; 
    }
    for(ll i=1;i<=n;++i)
    {
        char c;cin>>c;
        if(c=='M')
        {
            ll x=read(),y=read();
            M(x,y);
        }
        else
        {
            ll x=read();
            printf("%d\n",d[x]);
        }
    }
    return 0;
}