2.NOIP模拟赛试题详讲

A

题目描述:数轴上有n个棋子, 第i 个棋子初始在ai.你会不断对棋子进行操作, 每次操作是选择一个棋子, 假设它的坐标是x, 则可以把它移动到x−1或x−2, 但要求移动后的位置原先没有棋子.如果一个棋子的坐标变得小于等于0, 则称它挂掉了. 你需要求出有多少个排列p使得存在一种方案使得第i 个挂掉的棋子是pi. 答案对109+ 7取模.

解析
1.假如两个棋子位于101,103,和两个棋子位于1,3是没有区别的,可以101先挂掉,也可以以103先挂掉。
2.假如位于2,3,4,那么2,3就会堵住4,所以2,3必须挂掉一个。
3.假如位于1,3,5,那么3个数都可以畅通无阻,排列方式就是全排列 n ! n! n!(排列数公式)。

我们分析了以上3种情况,那么我们考虑维护一个序列1,3,5,7,…,2i-1,如果a[i]==2i,那么我们要将前面的一个挂掉,序列保持不变,如果a[i]>=2i-1,那么统计方案数。

for(int i=1;i<=n;i++)
{
    
    
	ans=ans*i%mod;
}

求全排列,直接累乘。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 

const int mod=1e9+7;
int n,a[100005];

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	ll ans=1;
	vector<int>b(1,1);//表示向量[1]==1
	for(int i=2;i<=n;i++)
	{
    
    
		if(a[i]!=b.back()+1)//不在2*i的位置
		{
    
    
			b.push_back(b.back()+2);
		}
		else//在的话,那么挂掉前面的一个,至于哪一个,都可以
		{
    
    
			ans=ans*(b.size()+1)%mod;
		}
	}
	for(int i=1;i<=b.size();i++)
	{
    
    
		ans=ans*i%mod;
	}
	cout<<ans;
	return 0;
}

B

题目描述:有一个长度为a+b+c+d+e的01串, 它由a个1, b个0, c个1, d个0, e个1拼接起来组成.有m个区间[li,ri]. 你可以进行若干次操作, 每次操作选择一个区间[li,ri] 并将[li,ri] 取反, 花费的代价为ri −li+ 1.求将这个串变成全1串的最小代价. 无解输出−1.

解析
它是有5个区间组成,要将所有01串的都翻转太复杂了,不妨差分的思想,表示该位置和下一个位置的数字是否相同,相同为0,不同为1。例如a,b,c,d,e分别等于1,2,3,4,5。即100111000011111,差分后为101001000100000,对应了下标a,a+b,a+b+c,a+b+c+d。
重点: 如果我们要全部为1,即把差分的变成000000000000…,所以要让差分01串中的第1到第2的最短+第3到第4的最短,或者第1到第3+第2到第4,或者第1到第4+第2到第3,咦?这不就有点像最短路问题了吗,那就用这个思路,将这4个1作为点,每个取反的区间[l,r]为边,表示从l-1到r边权为r-l+1的边,来建立无向图,跑3遍dijstrar就解决了问题。
所以问题就转换到图上了 (太妙了)

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

#define num ch-'0'
void get(int &res)
{
    
    
    char ch;bool flag=0;
    while(!isdigit(ch=getchar()))
        (ch=='-')&&(flag=true);
    for(res=num;isdigit(ch=getchar());res=res*10+num);
    (flag)&&(res=-res);
}

#define N 1000005
#define ll long long 
int a,b,c,d,e,m;
ll edge[10][10];
ll dis[N],sta;
bool vis[N];
int first[N],nex[N],to[N],w[N],tot;

inline void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}


inline void dij(int sta)
{
    
    
	priority_queue<pair<int,int> >q;
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[sta]=0;
	q.push(make_pair(0,sta));
	
	while(!q.empty())
	{
    
    
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		else vis[x]=1;
		
		for(int i=first[x];i;i=nex[i])
		{
    
    
			int y=to[i];
			if(dis[y]>dis[x]+w[i])
			{
    
    
				dis[y]=dis[x]+w[i];
				q.push(make_pair(-dis[y],y));
			}
		}
	}
}

int main()
{
    
    
	get(a);get(b);get(c);get(d);get(e);get(m);

	for(int i=1;i<=m;i++)
	{
    
    
		int l,r;
		get(l);get(r);
		add(l-1,r,r-l+1);
		add(r,l-1,r-l+1);
	}
	
	dij(a);
	edge[1][2]=dis[a+b];
	edge[1][3]=dis[a+b+c];
	edge[1][4]=dis[a+b+c+d];
	
	dij(a+b);
	edge[2][3]=dis[a+b+c];
	edge[2][4]=dis[a+b+c+d];
	
	dij(a+b+c);
	edge[3][4]=dis[a+b+c+d];
	
	ll ans=0x3f3f3f3f3f;
	ans=min(edge[1][2]+edge[3][4],min(edge[1][3]+edge[2][4],edge[1][4]+edge[2][3]));
	
	if(ans!=0x3f3f3f3f3f) cout<<ans;
	else cout<<-1;
	return 0;
}

C

第一行三个正整数n,m, k.
接下来m行, 每行三个整数ui,vi,wi 表示一条连接(ui,vi), 长度为wi 的边.
对于所有数据, 满足2≤n≤106,1≤m,k≤106,min(n,m, k)≤5,1≤wi ≤108

首先观察数据,min(n,m,k)<=5
显然如果n取最小,那么n<k,不可能;如果m取最小,那么m<k,也不可以。
那么我们只需讨论k<5的情况了
我们必须要分5种类讨论吗?
不一定
我们只需在n这个点的后面连上边权为0的点n+1,n+2…
我们就都转化为了k==5的情况了
那又要怎么搞呢?
5=2+2+1,我们可以枚举到1经过边的数量为2的点和到n边的数量为2的点,中间再枚举边,把它们连起来更新答案。至于从1开始的2条边和从n开始的2条边中,我们只需要找前3小的边就可以了。
下面是std代码PS:我还没写完

#include <bits/stdc++.h>

using namespace std;

const int INF = 0x1f3f3f3f;

int main() {
    
    
  freopen("c.in", "r", stdin);
  freopen("c.out", "w", stdout);
  ios::sync_with_stdio(false);
  cin.tie(0);
  int n, m, k;
  cin >> n >> m >> k;
  if (k >= n || k > m) {
    
    
    cout << -1 << "\n";
    return 0;
  }
  vector<vector<pair<int, int>>> g(n + 5 - k);
  for (int i = 0; i < m; ++i) {
    
    
    int v, u, w;
    cin >> v >> u >> w;
    --v; --u;
    g[v].emplace_back(u, w);
    g[u].emplace_back(v, w);
  }
  for (int i = 0; i < 5 - k; ++i) {
    
    
    g[n + i - 1].emplace_back(n + i, 0);
    g[n + i].emplace_back(n + i - 1, 0);
  }
  n += 5 - k;
  auto init = [&](int v) {
    
    
    vector<vector<pair<int, int>>> dist(n, vector<pair<int, int>>(3, make_pair(INF, -1)));
    for (auto e1 : g[v]) {
    
    
      int u1 = e1.first, w1 = e1.second;
      if (u1 == 0 || u1 == n - 1) {
    
    
        continue;
      }
      for (auto e2 : g[u1]) {
    
    
        int u2 = e2.first, w2 = e2.second;
        if (u2 == 0 || u2 == n - 1) {
    
    
          continue;
        }
        pair<int, int> r(w1 + w2, u1);
        if (r < dist[u2][0]) {
    
    
          dist[u2][2] = dist[u2][1];
          dist[u2][1] = dist[u2][0];
          dist[u2][0] = r;
        } else if (r < dist[u2][1]) {
    
    
          dist[u2][2] = dist[u2][1];
          dist[u2][1] = r;
        } else if (r < dist[u2][2]) {
    
    
          dist[u2][2] = r;
        }
      }
    }
    return dist;
  };
  vector<vector<pair<int, int>>> foo = init(0);
  vector<vector<pair<int, int>>> bar = init(n - 1);
  int ans = INF;
  for (int v = 1; v < n - 1; ++v) {
    
    
    for (auto e : g[v]) {
    
    
      int u = e.first, w = e.second;
      if (u == 0 || u == n - 1) {
    
    
        continue;
      }
      int pv = 0, pu = 0;
      if (foo[v][pv].second == u) {
    
    
        ++pv;
      }
      if (bar[u][pu].second == v) {
    
    
        ++pu;
      }
      if (foo[v][pv].second != bar[u][pu].second) {
    
    
        ans = min(ans, foo[v][pv].first + bar[u][pu].first + w);
      } else {
    
    
        int qv = pv + 1, qu = pu + 1;
        if (foo[v][qv].second == u) {
    
    
          ++qv;
        }
        if (bar[u][qu].second == v) {
    
    
          ++qu;
        }
        ans = min(ans, foo[v][pv].first + bar[u][qu].first + w);
        ans = min(ans, foo[v][qv].first + bar[u][pu].first + w);
      }
    }
  }
  cout << (ans == INF ? -1 : ans) << "\n";
  return 0;
}

.

猜你喜欢

转载自blog.csdn.net/pigonered/article/details/120814348