2020.6.10

1.第一题 (eat.cpp/c/pas)

[问题描述]

凶猛的XX出来吃人了!

每天早晨,XX从大山里出来,到达一个城市,然后花费一整天的时间把这座城市里的人 吃光。直到夜晚,XX才回到山中去。当**经过一个城市时,不管是否吃人,它都会把这座城 市彻底破坏,以至于下次不能再到这个城市吃人了。

​显然,城市里的居民无法忍受这样的状况。所以,每天夜晚,每座城市里都会有一个人 逃到乡下去,到了乡下以后XX就永远吃不到他了。

城市之间有一些双向道路连接着。其中 1 号城市连接着大山,即XX每天的旅途的起点。 当然,XX只能沿着这些道路走。 XX意识到必须抓紧时间吃人,所以它每天都要认真选取要去的城市,当然它不能选择已 经被吃过或破坏过的城市。现在问题来了,在所有城市没有人居住之前,XX最多能吃掉多少 人?

【输入】

​ 输入文件名为 eat.in。

​ 第一行两个整数𝑛, 𝑚,用一个空格隔开,表示城市的个数和道路数。

​ 第二行𝑛个整数𝑎𝑖,表示每座城市初始的人数。两个数之间用一个空格隔开。

​ 接下来𝑚行,每行两个整数𝑢, 𝑣(1 ≤ 𝑢, 𝑣 ≤ 𝑛, 𝑢 ≠ 𝑣),用一个空格隔开,表示城市𝑢和城 市𝑣之间有一条双向道路。城市 1 和大山之间也有一条双向道路。 数据保证所有城市都存在到城市 1 的路径。

【输出】

​ 输出文件名为 eat.out。 输出共一行一个整数,表示在所有城市没有人居住之前,**最多能吃掉的人数。

输入 输出
5 5
1 3 2 4 7
1 2
1 3
2 3
2 4
3 5
11

【数据说明】

​ 对于 10%的数据,1 ≤ 𝑛 ≤ 5,0 ≤ 𝑚 ≤ 10,0 ≤ 𝑎𝑖 ≤ 5。

​ 对于 30%的数据,1 ≤ 𝑛 ≤ 200,0 ≤ 𝑚 ≤ 500,0 ≤ 𝑎𝑖 ≤ 200。

​ 对于 60%的数据,1 ≤ 𝑛 ≤ 2,000,0 ≤ 𝑚 ≤ 10,000,0 ≤ 𝑎𝑖 ≤ 20,000。

​ 对于 100%的数据,1 ≤ 𝑛 ≤ 200,000,0 ≤ 𝑚 ≤ 2,000,000,0 ≤ 𝑎𝑖 ≤ 2,000,000。

所以怎么做呢?

考虑贪心: 吃的最多本着上就是走的人最少, 那么就意味着要最先吃掉人最多的地方, 能够有机会离开的人才最少

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll Maxn = 2e5 + 10;
ll N, M, Ans;
ll Num[Maxn]; 

bool cmp(ll a, ll b) {return a > b;}

int main()
{
    freopen ("eat.in", "r", stdin);
    freopen ("eat.out", "w", stdout);
    scanf ("%lld %lld", &N, &M);
    for (ll i = 0; i < N; ++i) scanf ("%lld", Num + i);
    sort (Num, Num + N, cmp);
    for (ll i = 0; i < N; ++i)
    {
        if (Num[i] <= i) break;
        Ans += (Num[i] - i);
    }
    printf ("%lld\n", Ans);
}//一定要开long long, 后面40分爆int了!!!

2.整数划分 (division.cpp/c/pas)

【问题描述】

​ BG 得到了一个整数𝑁,他想要把𝑁分解成若干个小整数的乘积。

​ BG 给出了他的分解规则:

  • 分解出的整数必须来自集合𝑆;
  • 分解出的整数必须互不相同,且两两互质。 现在给出整数𝑁,集合大小𝑀和集合𝑆,求 BG 有多少种分解方法。

【输入】

输入文件名为 division.in。

​ 第一行两个整数𝑁, 𝑀,表示要分解的数和集合大小。

​ 第二行包含𝑀个互不相同的整数𝑎𝑖,描述了集合𝑆,即𝑆 = {𝑎1, 𝑎2, 𝑎3, … , 𝑎𝑀}。

【输出】

​ 输出文件名为 division.out。

​ 输出共一行一个整数,表示方案数。

​ 数据保证答案在 64 位带符号整数范围内。 如果没有方案,输出一个 0。

输入 输出
12 5
2 3 4 5 6
1
42 8
1 2 3 6 7 14 21 42
10

【输入输出样例说明】

​ 共 10 种方案:42,1 × 42,2 × 21,3 × 14,6 × 7,1 × 2 × 21,1 × 3 × 14,1 × 6 × 7, 2 × 3 × 7 和 1 × 2 × 3 × 7。

打一个表:

2 2
3 6
5 30
7 210
11 2310
13 30030
17 510510
19 9699690
23 223092870
29 6469693230
31 200560490130
37 7420738134810
41 304250263527210
43 13082761331670030
47 614889782588491410
53 -4304329670229058502

当我们乘到第\(16\)的质数时已经是爆\(long \; long\)了, 那么就是说\(N\)的不同的质因子最多最多就只有\(15\)

那么再看我们的性质\(2\) : 分解出的整数必须互不相同,且两两互质

那么说明了什么?

换句话讲, 就是说你每次分解出的整数中, 至少包含了一个质因子, 且这个质因子的所有指数一定会全部被分解出去

否则不能保证分解出的所有整数能够两两互质

那么剩下的就是一个状压DP惹

#include <bits/stdc++.h>

using namespace std;

typedef  long long ll;
const int Maxn = 1e4 + 5;
int M, P[Maxn], Q[Maxn], S[Maxn >> 2], p[Maxn >> 2][20], q[Maxn >> 2][20];//p[i][j],q[i][j]分别表示i的第j个质因子的底数和指数, S是记录i有多少个质因子
ll N, f[1 << 20];
map <int, int> F;//F[i] 表示质因子i的指数的值

int main()
{
    freopen ("division.in", "r", stdin);
    freopen ("division.out", "w", stdout);
    scanf ("%lld %d", &N, &M);
    int n(0), nn(0);
    for (int i = 1; i <= M; ++i)
    {
        int X;
        scanf ("%d", &X);
        for (int j = 2; j * j <= X; ++j)
            if (X % j == 0)
                for (p[i][++S[i]] = j, P[++nn] = j; X % j == 0; X /= j, q[i][S[i]]++);//记录S集合中的点的信息
        if (X > 1) p[i][++S[i]] = X, q[i][S[i]] = 1, P[++nn] = X;
    }
    for (int i = 1; i <= nn; ++i)
        if (N % P[i] == 0 && !F[P[i]])
        {
            P[++n] = P[i], F[P[i]] = n;
            for (; N % P[i] == 0; N /= P[i], Q[n]++);//分解N, 得到N的一些列质因子
        }
    if (N > 1) return puts("0") & 0;//如果N此时还有值, 显然S集合中的数是不可行的
    f[0] = 1;
    for (int i = 1; i <= M; ++i)
    {
        int X = 0;
        for (int j = 1; j <= S[i]; j++)
            if (F[p[i][j]] && Q[F[p[i][j]]] == q[i][j]) X |= 1 << (F[p[i][j]] - 1);
            else 
            {
                X = -1; 
                break;
            }
        if (X > -1) 
            for (int j = (1 << n) - 1; j >= 0; --j) 
                if (!(j & X)) f[j | X] += f[j];//累加贡献
    }
    printf ("%d\n", f[(1 << n) - 1]);//顺利切掉
}

考场上想的是暴力:

就是在输入\(S\)集合的时候,只保留\(N\)的因子,然后 伪\(Dp\)求解:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
map <ll, ll> Dp;
priority_queue <ll> K;
ll N, M, Temp;
bool GRD;

inline ll Gcd(ll X, ll Y)
{
    if (Y == 0) return X;
    return Gcd (Y, X % Y);
}

int main()
{
    freopen ("division.in", "r", stdin);
    freopen ("division.out", "w", stdout);
    scanf ("%lld %lld", &N, &M);
    for (int i = 1; i <= M; ++i) 
    {
        scanf ("%lld", &Temp);
        if (Temp == 1) GRD = 1;
        if (Temp <= N && Gcd(N, Temp) != 1) K.push(Temp);
    }
    Dp[1] = 1;
    while (!K.empty())
    {
        ll Now = K.top();
        K.pop();
        map <ll, ll> :: iterator P = Dp.begin();
        while (P != Dp.end())
        {
            int NewK = (P -> first) * Now;
            if ((Gcd(N, NewK) == 1) || (NewK > N) || (Gcd(Now, P -> first) != 1)) 
            {
                ++P;
                 continue;
            }
            Dp[NewK] += P -> second;
            ++P;
        }
    }
    if (GRD) Dp[N] *= 2;
    printf ("%lld\n", Dp[N]);
}

为什么这份代码当时没有AC呢,非常的简单:

首先:

if (Temp <= N && Gcd(N, Temp) != 1) K.push(Temp);

这个就错了, \(gcd(N,Temp)\;!= \;\&\& \; gcd(N, Temp) == Temp\)才能保证Temp是N的约数

然后还有个\(ZZ\)错误:

int NewK = (P -> first) * Now;

\(int\)显然是爆掉了的

改了以后竟然就有90分了

\(TLE\)\(2\)个点,其实这两个点就是在输入的时候,判断这个数是否取尽了\(N\)的质因数的指数

可能这样写着不太明白?

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
map <ll, ll> Dp;
priority_queue <ll> K;
ll N, M, Temp;
bool GRD;

inline ll Gcd(ll X, ll Y)
{
   if (Y == 0) return X;
   return Gcd (Y, X % Y);
}

inline bool Check(ll X)
{
   if (X == 1) return 0;
   ll Temp = N, B = N;
   for (ll i = 2; i * i <= B; ++i)
   {
       if (X % i) continue;
       while (X % i == 0 && Temp % i == 0) X /= i, Temp /= i;
       if ((Temp % i == 0) || (X % i == 0)) return 0;//只要有一个素数的指数不相等就是不可行的方案
   }
   if (X > 1) return (Temp >= X) && (Temp % X == 0);
   return (Temp >= X);
}//就是加了一个这个,Temp必须取完N中Temp有的所有素数,然后95了

int main()
{
   freopen ("division.in", "r", stdin);
   freopen ("division.out", "w", stdout);
   scanf ("%lld %lld", &N, &M);
   for (int i = 1; i <= M; ++i) 
   {
       scanf ("%lld", &Temp);
       if (Temp == 1) GRD = 1;
       if (Check(Temp)) K.push(Temp);
   }
   Dp[1] = 1;
   while (!K.empty())
   {
       ll Now = K.top();
       K.pop();
       map <ll, ll> :: iterator P = Dp.begin();
       while (P != Dp.end())
       {
           ll NewK = (P -> first) * Now;
           if ((NewK > N) || (Gcd(Now, P -> first) != 1)) 
           {
               ++P;
                continue;
           }
           Dp[NewK] += P -> second;
           ++P;
       }
   }
   if (GRD) Dp[N] *= 2;
   printf ("%lld\n", Dp[N]);
}

其实个人认为这个算法理论上是可以过的,但是只有95,猜测是Map遍历太慢了

其实就是我太菜了


3.观光旅行 (trip.cpp/c/pas)

【问题描述】

BG 来到了一个美丽的风景区旅行,这个景区共有𝑛个景点,编号从1到𝑛。这𝑛个景点之 间共有𝑚条双向的观光道路,每条观光道路都有一个魅力值,第𝑖条观光道路的魅力值为𝑤𝑖。

现在 BG 想从任意一个景点出发,沿着一条路径旅行其它景点。为了避免旅途的枯燥, BG 每次经过的道路的魅力值都要严格大于之前经过的任何道路。同时,他想让旅途尽可能 长。

请问在满足他的要求的情况下,路径的最长长度是多少,并求出不同的最长路径共有多 少条。路径的长度即它经过的道路数。两条路径被认为不同,当且仅当它们经过的景点序列 不同。为了防止输出的数字过大,你只需输出路径数对\(1,000,000,007\)取模的结果。

【输入】

输入文件名为 trip.in。

输入文件的第一行有两个用一个空格隔开的整数𝑛、𝑚,表示该景区有𝑛个景点和𝑚条观 光道路。

接下来𝑚行每行三个整数𝑢𝑖 , 𝑣𝑖 , 𝑤𝑖,每两个整数之间用一个空格隔开,表示第𝑖条道路的 魅力值为𝑤𝑖,在景点𝑢𝑖和𝑣𝑖之间。 输入数据保证𝑢𝑖 ≠ 𝑣𝑖,两个景点之间最多只有一条观光道路,第一问的答案至少为 2。

【输出】

输出文件名为 trip.out。

输出的第一行一个整数,表示最长的路径长度。 输出的第二行一个整数,表示不同的最长路径数对\(1,000,000,007\)取模的结果。

输入 输出
5 5
1 2 4
1 3 5
2 4 3
2 3 1
2 5 2
3
3

【输入输出样例说明】

共有 3 条长度为 3 的合法路径:

3—2—1—3,经过道路的魅力值为 1、4、5;

4—2—1—3,经过道路的魅力值为 3、4、5;

5—2—1—3,经过道路的魅力值为 2、4、5。

【数据说明】

对于 10%的数据,1 ≤ 𝑛 ≤ 10,1 ≤ 𝑚 ≤ 20;

对于 30%的数据,1 ≤ 𝑛 ≤ 100,1 ≤ 𝑚 ≤ 500;

对于 60%的数据,1 ≤ 𝑛 ≤ 1,000,1 ≤ 𝑚 ≤ 3,000;

对于 80%的数据,1 ≤ 𝑛 ≤ 2,000,1 ≤ 𝑚 ≤ 100,000;

对于 100%的数据,1 ≤ 𝑛 ≤ 50,000,1 ≤ 𝑚 ≤ 200,000,0 ≤ 𝑤𝑖 ≤ 109。

所以对于这道题,考试的时候我爆零了,that is 我太菜了!!!

怎么处理路径上的边权单调上升呢?

考虑:

从小的边到大的边再建一条新的边,易证得到的新图是一个DAG

怎么建图?

首先有出边\(j\)的虚点\(j'\)\(j\) 建一条边权为\(1\)的边那么只要经过了这个点,就会对路径长度产生贡献

从入边向边权比它大的边的虚边建一条边权为\(0\)的点

然后再拓扑一便就统计答案即可

#include <bits/stdc++.h>
#define Pr pair<int, int>

using namespace std;

const int Maxn = (2e5 + 5) * 4, Mod = 1e9 + 7;
int n, N, M, Cur, X, Y, Z;
int F[Maxn], G[Maxn], In[Maxn], Q[Maxn];
int Head[Maxn], E[Maxn << 1], W[Maxn << 1], Next[Maxn << 1];
vector < Pr > V[Maxn];

inline void AddEdge(int U, int V, int A)
{
	Next[++Cur] = Head[U];
	Head[U] = Cur;
	E[Cur] = V;
	W[Cur] = A;
	In[V]++;
}

inline void UpDate(int a, int b, int &c, int &d)
{
	if (a > c) c = a, d = b;
	else if (a == c) d = (d + b) % Mod;
}

inline void Build()
{
	n = (M + 1) << 1;
	for (int i(1), t(0); i <= N; ++i)
	{
		sort (V[i].begin(), V[i].end());
		for (int j(0), k(0); j < V[i].size(); ++j)
		{	
			AddEdge(n + j, V[i][j].second, 1);//从j'到j建一条1的边
			while (k < V[i].size() && V[i][j].first == V[i][k].first) ++k;//保证严格单调
			if (k < V[i].size()) AddEdge(V[i][j].second ^ 1, n + k, 0);//从另一条边进来,从这条边(k)出去
			if (j) AddEdge(n + j - 1, n + j, 0);//因为都是同一个点的边,所以可以0贡献换边
		}
		n += V[i].size();
	}
}

int main()
{
	freopen ("trip.in", "r", stdin);
	freopen ("trip.out", "w", stdout);
	scanf ("%d %d", &N, &M);
	for (int i = 1; i <= M; ++i)
	{
		scanf ("%d %d %d", &X, &Y, &Z);
		V[X].push_back(Pr(Z, i << 1));
		V[Y].push_back(Pr(Z, i << 1 | 1));
	}
	Build();
	int L(1), R(0);
	for (int i = 2; i <= n; ++i) 
		if (!In[i]) F[i] = 0, G[i] = 1, Q[++R] = i;
	for (; L <= R; ++L)
		for (int u = Q[L], i = Head[u], v; v = E[i], i; i = Next[i])
		{
			UpDate(F[u] + W[i], G[u], F[v], G[v]);
			if (!--In[v]) Q[++R] = v;
		}
	int X(0), Y(0);
	for (int i = 2; i <= n; ++i) UpDate(F[i], G[i], X, Y);
	printf ("%d\n%d\n", X, Y);
}//切掉惹

总结:

细节处理还是有问题,比如说\(T1,T2\) 没有开\(long\;long\)少了整整\(60\)

然后加上对\(T2\)的优化又多\(5\)分, 也就是说理论应该是\(200\)分, 实际得分\(125\)

还要加油呀😆

posted @ 2020-06-11 21:20  BlogOfASBOIER  阅读(278)  评论(0)    收藏  举报