【題解報告】ZJNU綜合訓練(2021.1.26)
阿新 • • 發佈:2021-01-27
【題解報告】ZJNU綜合訓練(2021.1.26)
- 綜合訓練 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
時間複雜度: 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]=1or0就可以了。
時間複雜度: 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 − c ∣ < p |i-c|<p ∣i−c∣<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 題呀。。
如果先手第一個盤子都放不下,那肯定是後手贏了。
否則,先手第一個盤子放在該桌子的正中心處。
這樣,構造除了一個中心對稱圖形。後手不論怎麼放,先手都放在該位置的中心對稱位置處。這樣,不論後手怎麼放,先手都能有位置放(易證),因為每一次先手放置的時候該圖都是中心對稱圖形。