- 综合训练 ZJNU综合训练(2021.1.26)
B:DP+搜索 | CF 1057C
C:组合数学+dp | CF 1422C
D:思维 | CF 1185D
F:树形DP | CF 219D
H:暴力+数学 | CF 1096C
I:思维+DP | CF 1381B
J:DP | CF1452D
M:博弈 | CF 197A
B
- 有 n n n 盒糖果并排放,每盒有数量 r i r_i ri ,里面颜色为 c i c_i ci。
- 如果吃糖,必须在该位置的糖全部吃完,且必须比上一次吃得数量多,且必须与上一次吃的糖的颜色不一样。
- 每次向相邻方向走一格的时间花费为 1 1 1,吃糖不花费时间。
起点在 s s s,问你至少吃 k k k 颗糖最少花费时间为多少。
- 题目要求真地多,而且我开始还读错题意了。。
设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示你现在在位置 i i i 且吃了该位置的糖,花了 j j j 时间的最多吃糖数。
为什么可以这么设呢?因为你每次吃糖一定是吃完的,你中间如果走过去没有吃得话,其实是为了走到目的地然后吃,所以设的不是你目前的位置,而是你目前吃完糖的位置。 - 转移条件也很简单,满足糖果颜色不同、数量要更多就行了。
注意,时间上限是多少? n 2 n^2 n2,因为你不可能没吃糖,经过同一个点两次。
时间复杂度: O ( n 4 log n ) O(n^4\log n) O(n4logn)
const int MAX = 55;
int n,s,k;
int r[MAX];
string c;
int dp[MAX][MAX*MAX];
int main()
{
cin >> n >> s >> k;
for(int i = 1;i <= n;++i)cin >> r[i];
cin >> c;
c = " " + c;
memset(dp,-1,sizeof(dp));
priority_queue<pair<int,int> >Q;
for(int i = 1;i <= n;++i){
dp[i][abs(i-s)] = r[i];
Q.push(make_pair(-(abs(i-s)),i));
}
while(!Q.empty()){
int d = -Q.top().first;
int x = Q.top().second;
Q.pop();
if(dp[x][d] >= k){
cout << d;
return 0;
}
for(int i = 1;i <= n;++i){
if(c[x] != c[i] && r[i] > r[x] && dp[i][abs(i-x)+d] < dp[x][d] + r[i]){
dp[i][abs(i-x)+d] = dp[x][d] + r[i];
Q.push(make_pair(-(abs(i-x)+d),i));
}
}
}
cout << -1;
return 0;
}
C
- 给你一个长度为 1 0 5 10^5 105 的数字串。
问你,中间连续删掉一段数字之后,剩下的数字头尾相接,形成删数。问你所有不同的删法后,所拼接出来的删数的和取模 998244353 998244353 998244353 是多少。 - 比如 107 107 107,删掉中间连续一段之后会变成 07 、 17 、 10 、 7 、 1 、 0 07 、 17、 10、 7、 1、 0 07、17、10、7、1、0,他们的和为 42 42 42
- 把数字拼接起来之后再算怎么搞都是搞不出来的、、我们按头和尾来考虑。
如果他们俩能连接起来,我们给他们连一条线。下面的 + + +号表示把他们收尾拼接。
比如 ϕ + 07 = 07 \phi +07=07 ϕ+07=07、 ϕ + ϕ = 0 \phi+\phi=0 ϕ+ϕ=0、 1 + 7 = 17 1+7=17 1+7=17
- 考虑每一串头和每一串尾到底对答案作出多少贡献。
注意:头的拼接还要考虑拼接后相当于头的数字增加了 1 0 k 10^k 10k - 尾的收益:最长的尾只贡献 1 1 1次,次长的贡献 2 2 2次等
尾的收益比较好算,单个串的数字好算,单个串的贡献次数也好算。 - 头的收益:
(1) ϕ \phi ϕ 的贡献为 1 1 1 次和长度为 2 2 2 的串拼接, 1 1 1 次为和长度为 1 1 1 的串拼接, 1 1 1 次和长度为 0 0 0 的串拼接,它的贡献就是 0 × ( 1 0 2 + 1 0 1 + 1 0 0 ) 0\times(10^2+10^1+10^0) 0×(102+101+100)
(2) 1 1 1 的贡献为 1 1 1 次和长度为 1 1 1 的串拼接, 1 1 1 次和长度为 0 0 0 的串拼接,它的收益就是 1 × ( 1 0 1 + 1 0 0 ) 1\times(10^1+10^0) 1×(101+100)
(3)对于某一个头串,它的贡献为 它 本 身 × p r e [ x ] 它本身\times pre[x] 它本身×pre[x] ,其中 p r e [ x ] = 1 0 x + 1 0 x − 1 + ⋯ + 1 0 0 pre[x]=10^x+10^{x-1}+\cdots+10^0 pre[x]=10x+10x−1+⋯+100。这个 x x x 取原串长减去该头串长再减一。
时间复杂度: O ( l o g 10 S ) O(log_{10}S) O(log10S)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
char ss[MAX];
ll pre[MAX];
void init(int x){
pre[0] = 1;
ll base = 10;
for(int i = 1;i <= x;++i){
pre[i] = (pre[i-1] + base) % MOD;
base = base * 10 % MOD;
}
}
int main()
{
init((int)1e5+50);
scanf("%s",ss);
int len = strlen(ss);
ll sum = 0;
ll base = 1;
ll tmp = 0;
for(int i = len-1;i >= 1;--i){
tmp = (base * (ss[i]-'0') % MOD + tmp) % MOD;
sum = (sum + tmp * i % MOD) % MOD;
base = (base * 10) % MOD;
}
tmp = 0;
base = 1;
for(int i = 0;i < len-1;++i){
tmp = (tmp * 10 % MOD + (ss[i]-'0')) % MOD;
sum = (sum + tmp * pre[len-i-2] % MOD) % MOD;
base = (base * 10) % MOD;
}
printf("%lld",sum);
return 0;
}
D
- 问你对于一个序列,是否可以删掉一个数字后,其他数字成等差,问你删掉哪一个。
- 序列先排序,再考虑删头或者删尾或者删中间。
删中间的话:要删除的那个数字的序号一定是 该位置之后的数字减去该位置之前的数字的和最小。
考虑一个等差 5 、 10 、 15 、 20 、 25 5、 10、 15、 20、 25 5、10、15、20、25
如果要删中间,你多余的数字应该要放在这些数字的中间
5 、 10 、 15 、 18 、 20 、 25 5、 10、 15、 18、 20、 25 5、10、15、18、20、25
此时明显 20 − 18 + 18 − 15 = 20 − 15 = 5 20-18+18-15=20-15=5 20−18+18−15=20−15=5 是所有里面最小的,因为其他的数字 15 − 5 = 10 15-5=10 15−5=10 明显是两倍等差。
F
- 给你一个树,但是树的边是单向的。
你要选择首都的位置,要修改一些道路的朝向,使得首都能够到达所有的位置。
问你最少修改多少条路,以及首都的所有可选位置。 - 树形 D P DP DP。设 d p [ x ] dp[x] dp[x] 表示在 x x x 位置处选择首都的话,需要改多少条路。
虽然这个 d p [ x ] dp[x] dp[x] 我们是直接算不出来的,但是我们知道相邻两个位置的 d p dp dp 值的大小关系
- 这样就能算出哪个节点的 d p dp dp 值相对最小,然后以该节点作为起点进行 d f s dfs dfs,查看需要修改几条边就行了。
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
vector<pair<int,int> >V[MAX];
int dp[MAX];
int mn,id;
void dfs(int u,int fa){
/// 跑dp
if(dp[u] < mn){
mn = dp[u];
id = u;
}
for(auto it : V[u]){
int v = it.first;
int w = it.second;
if(v == fa)continue;
if(w == 1)dp[v] = dp[u] + 1;
else dp[v] = dp[u] - 1;
dfs(v,u);
}
}
int ans;
void dfs2(int u,int fa){
// 跑最小修改路径数量
for(auto it : V[u]){
int v = it.first;
int w = it.second;
if(v == fa)continue;
if(w == -1)ans++;
dfs2(v,u);
}
}
int main()
{
int n;scanf("%d",&n);
for(int i = 1;i < n;++i){
int ta,tb;scanf("%d%d",&ta,&tb);
V[ta].push_back(make_pair(tb,1));
V[tb].push_back(make_pair(ta,-1));
}
mn = 0;id = 1;
dfs(1,1);
dfs2(id,id);
printf("%d\n",ans);
for(int i = 1;i <= n;++i){
if(dp[i] == dp[id])cout << i << " ";
}
return 0;
}
H
- 问你,最少正几边形,你通过顶点连接能够得到 d d d 的角度(角度制),该角度为 1 ∼ 180 1\sim180 1∼180的整数。
- 圆周角所对的弧长相等的话,圆周角是相同的。因此一个正 n n n 边形,算出一份圆周角是多少度,然后每次暴力枚举份数 1 ∼ n − 2 1\sim n-2 1∼n−2,查看是不是整数度数即可。
int mn[MAX];
int main()
{
int T;cin >> T;
while(T--){
int n;cin >> n;
int mn = INF;
for(int i = 3;i <= 700;++i){
double deg = (180.0*i-360)/(1.0*i)/(1.0*i-2);
for(int j = 1;j <= i-2;++j){
double de = deg * j;
if(fabs(de-n)<EPS)mn=min(mn,i);
}
}
if(mn == INF)cout << -1 << endl;
else cout << mn << endl;
}
return 0;
}
I
- m e r g e ( A , B ) merge(A,B) merge(A,B) 函数表示每次取 A 、 B A、B A、B 序列的开头较小的元素,然后把该位置的元素拿出来,放在集合的尾。如果有一个序列是空的话,直接取非空序列。
比如 m e r g e ( { 3 , 2 } , { 1 , 4 } ) = { 1 , 3 , 2 , 4 } merge(\{3,2\},\{1,4\})=\{1,3,2,4\} merge({ 3,2},{ 1,4})={ 1,3,2,4}
给定一个 2 n 2n 2n 个元素的全排列。问该全排列是否能通过 m e r g e ( A , B ) merge(A,B) merge(A,B),且 ∣ A ∣ = ∣ B ∣ = n |A|=|B|=n ∣A∣=∣B∣=n得到。
- 非常巧妙的一题。
(1)对于目前的最大元素 2 n 2n 2n ,假设该元素原来是在 A A A 里面,那么该位置之后的所有数字一定全属于 A A A 序列。然后我们把这些数字给拿出来。
(2)对于剩下的数字,有最大元素 p p p ,该位置到末尾的位置一定全属于 A A A 或 B B B 序列。
(3)重复上述步骤,我们就得到了一段一段的序列,每一段必须都属于 A A A 或者 B B B 序列。
(4)怎么求是否能存在 ∣ A ∣ = ∣ B ∣ = n |A|=|B|=n ∣A∣=∣B∣=n 呢?设一个 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示目前处理到第 i i i 段,若 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1 则表示目前我们能拿到长度为 j j j 的段。最后看 d p [ 段 数 ] [ n ] = 1 o r 0 dp[段数][n]=1\ or\ 0 dp[段数][n]=1 or 0就可以了。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
int aa[MAX];
int pos[MAX];
bool use[MAX];
int shu[MAX];
int dp[MAX][MAX];
int main()
{
int T;cin >> T;
while(T--){
int n;cin >> n;
int ed = n * 2;
for(int i = 1;i <= ed;++i){
cin >> aa[i];
pos[aa[i]] = i;
use[aa[i]] = 0;
}
int you = ed;
int last = ed;
int cnt = 0;
while(you){
while(use[last])last--;
for(int i = pos[last];i <= you;++i)use[aa[i]] = 1;
shu[++cnt] = you - pos[last] + 1;
you = pos[last] - 1;
}
for(int i = 1;i <= cnt;++i)
for(int j = 0;j <= ed;++j)
dp[i][j] = 0;
dp[0][0] = 1;
for(int i = 1;i <= cnt;++i){
for(int j = 0;j <= ed;++j){
if(j >= shu[i])dp[i][j] |= dp[i-1][j-shu[i]];
dp[i][j] |= dp[i-1][j]; /// 这个不要忘记转移
}
}
if(dp[cnt][n])puts("YES");
else puts("NO");
}
return 0;
}
J
- 有 n + 1 n+1 n+1个点,第 0 0 0个点的位置为 0 0 0,第 i i i 个点的位置为 i i i。
对于中间 1 ∼ n 1\sim n 1∼n的点,每个点都有 1 2 \frac{1}{2} 21 的概率放置一个信号塔,该点为 i i i ,信号塔的强度如果为 p p p ,则他能覆盖到 j j j 点的条件是 ∣ i − j ∣ < p |i-j|<p ∣i−j∣<p
所有塔的信号强度是你自己调整的,可以都不同。
求:有多少的概率使得你可以调整所有信号塔,使得 1 ∼ n 1\sim n 1∼n的所有点都只被一个信号塔的信号覆盖,且 0 0 0 和 n + 1 n+1 n+1 没有被信号覆盖。
- 可以想到,不管信号强度为多少,该信号塔能覆盖到的数量一定是奇数。
题目就转变为 d p ( i ) 2 n \frac{dp(i)}{2^n} 2ndp(i),其中 d p ( i ) dp(i) dp(i) 表示 i i i 能被拆成奇数的方案个数。(注意这里是顺序可换的方案)
比如 d p ( 3 ) = 2 dp(3)=2 dp(3)=2,因为 3 = 1 + 1 + 1 = 3 3=1+1+1=3 3=1+1+1=3
考虑状态转移方程就可以了:
d p ( i ) = d p ( i − 1 ) + d p ( i − 3 ) + ⋯ + d p ( ( x − ( 2 k + 1 ) ) > 0 ) dp(i)=dp(i-1)+dp(i-3)+\cdots+dp((x-(2k+1))>0) dp(i)=dp(i−1)+dp(i−3)+⋯+dp((x−(2k+1))>0)
就用一个前缀和 p r e [ x ] = d p ( x ) + d p ( x − 2 ) + ⋯ pre[x]=dp(x)+dp(x-2)+\cdots pre[x]=dp(x)+dp(x−2)+⋯ 就可以了。
时间复杂度: O ( N ) O(N) O(N)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
ll dp[MAX];
ll pre[MAX]; /// pre[i] = dp[i] + dp[i-2] + dp[i-4]...
int main()
{
int n;cin >> n;
dp[0] = 1;
pre[0] = 1;
dp[1] = 1;
pre[1] = 1;
for(int i = 2;i <= n;++i){
dp[i] = pre[i-1];
pre[i] = (pre[i-2] + dp[i]) % MOD;
}
cout << dp[n] * inv(qpow(2,n)) % MOD;
return 0;
}
M
- 一张 n × m n\times m n×m的桌子,有无穷多的盘子,盘子半径都为 r r r
两个人轮流放盘子,盘子不能重叠,不能边超出桌子。
问你先手必胜还是后手必胜
- 这题真不是水题也不是 B U G BUG BUG 题呀。。
如果先手第一个盘子都放不下,那肯定是后手赢了。
否则,先手第一个盘子放在该桌子的正中心处。
这样,构造除了一个中心对称图形。后手不论怎么放,先手都放在该位置的中心对称位置处。这样,不论后手怎么放,先手都能有位置放(易证),因为每一次先手放置的时候该图都是中心对称图形。