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\)
还要加油呀😆

浙公网安备 33010602011771号