校內 第一屆ACM校賽——正賽
雖然沒有參加這次校賽,但 第一屆 總是意義非凡的,要仔細總結!( •̀ ω •́ )✧
A
-
TAG:簽到題
別問我為什麼沒有題解,因為不需要題解o(≧口≦)o
A.cpp
#include<cstdio>
int main(){
puts("tjdl!");
return 0;
}
B
-
PZ's solution:
1. 注意到 一次移動一塊積木 ,且目標狀態為 所有積木堆的積木數量 均相等,
可以得到目標 積木堆的積木數量 一定為 \(\frac{\sum_{i=1}^{n}h_{i}}{n}\)即平均值;
2. 考慮到實際操作時,比平均值多的積木堆的積木 將會移給 比平均值少的積木堆,
所以實際只有 比平均值多的積木堆 會 對答案有貢獻;
3. 因為目標狀態必為 所有積木堆的積木數量 均相等,答案累加的值即為 比平均值多的積木堆的 多於平均值的數量,
即對第\(x\)堆 積木數量大於平均值的積木堆,其答案貢獻為 \(h_{x}-\frac{\sum_{i=1}^{n}h_{i}}{n}\);
-
TAG:模擬;簽到題
B.cpp
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,a[55],sum,ans; int main(){ while(scanf("%d",&n)&&n!=0){ sum=ans=0; for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum+=a[i]; sum/=n; for(int i=1;i<=n;++i) if(a[i]>sum) ans+=a[i]-sum; printf("%d\n",ans); } return 0; }
C
-
PZ's solution:
1. 如圖,可以發現矩陣的兩個性質:
(1)矩陣內數值 沿 主對角線 (從左上角到右下角) 對稱;
(2)矩陣內 數值大小 與 數值數量 呈負相關;
**2. **答案的計算可以從性質(2)入手,我們只觀察 矩陣右上方 這一半,
顯然,最小數值必為 \(1/n\) ,而其數量必為 \(1\);最大數值為 \(1\),其數量為 \(n\) ;
**3. **由此我們可以大膽猜想一個關係,即 \(數值1/x的數量為n-x+1\)
-
TAG:數學;簽到題
C.cpp
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n; double ans; int main(){ while(scanf("%d",&n)&&n!=0){ ans=0; for(int i=1;i<n;++i) ans+=i*1.0/(n-i+1.0); if(n>1) ans*=2.0; ans+=n; printf("%.2lf\n",ans); } return 0; }
D
-
TAG:模擬;簽到題
這道題也沒有題解,因為題意本身十分明朗,有具體程式碼實現疑問請見D.cpp╮(╯-╰)╭
D.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
string s;
int T,n;
void print(char x){ printf("%c",'a'<=x&&x<='z' ? x-'a'+'A' : x); }
int main(){
scanf("%d",&T);
getline(cin,s);
//T後有一個 '\n' 無法被scanf讀取,要提前用
//getline(cin,s); 讀取消掉它!
while(T--){
getline(cin,s);
n=s.size();
print(s[0]);
for(int i=1;i<n;++i)
if(s[i-1]==' ') print(s[i]);
putchar('\n');
}
return 0;
}
E
-
PZ's solution:
**1. **考慮到\(N<2020\),我們可以使用\(O(N^{2})\)的演算法;
**2. **通過兩重迴圈,尋找 分解成的兩個數\(i,j\) ,並可通過計算直接得到 第三個數為\(k=n-i-j\) ;
3. 我們要在 得到分解出的三個數 的判定時增加一些限制,要保證兩點:
(1)3個數 各不相同;
(2)此答案 不能重複出現;
-
TAG:數學;簽到題
E.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,n,ans;
bool check(int x){
bool f=1;
while(x){
if(x%10==2 || x%10==4){ f=0; break; }
x/=10;
}
return f;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
ans=0;
for(int i=1;i<=n;++i)
for(int j=1;j<i;++j){
int k=n-i-j;
if(i>j && j>k && k>0 &&
check(i) && check(j) && check(k) ) ++ans;
}
printf("%d\n",ans);
}
return 0;
}
F
-
PZ's solution:
1.考慮使用 \(BFS\) ,判斷\(0\)是否 可能 為被圍面積的起點,可以通過判斷 其上 和 其左 是否為\(1\);
2.在\(BFS\)過程中,可以發現,不被圍起來的\(0\) 一定會遍歷到邊界,通過此特點判斷 無效起點
3.實現和細節請見程式碼(¬‿¬)
-
TAG:BFS廣度優先搜尋
F.cpp
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
queue<int>qx,qy;
int fx[]={0,0,1,-1};
int fy[]={1,-1,0,0};
int a[15][15],nx,ny,res,ans;
bool vis[15][15],f;
int bfs(int x,int y){
res=0; f=0;
qx.push(x); qy.push(y); vis[x][y]=1; ++res;
while(!qx.empty()){
x=qx.front(); qx.pop();
y=qy.front(); qy.pop();
for(int i=0;i<4;++i){
nx=x+fx[i]; ny=y+fy[i];
if(vis[nx][ny]||a[nx][ny]==1) continue;
if((nx==1||nx==10||ny==1||ny==10)&&a[nx][ny]==0){ res=0; f=1; }
//雖然此處可以判斷 非法0起點,但仍讓其遍歷完所有的0
//因為我們設定了 vis[x][y] 來表示已經遍歷過的點,如果出現 非法邊界被遍歷過 的情況,就可能無法再 判斷非法0起點 了
if(a[nx][ny]==0&&!vis[nx][ny]&&2<=nx&&nx<=9&&2<=ny&&ny<=9){
if(!f) ++res;
vis[nx][ny]=1;
qx.push(nx); qy.push(ny);
}
}
}
return res;
}
int main(){
for(int i=1;i<=10;++i)
for(int j=1;j<=10;++j)
scanf("%d",&a[i][j]);
for(int i=2;i<=9;++i)
for(int j=2;j<=9;++j)
if(a[i][j]==0&&a[i-1][j]==1&&a[i][j-1]==1&&!vis[i][j])
ans+=bfs(i,j);
printf("%d",ans);
return 0;
}
G
-
PZ's solution:
1. 首先考慮貪心,對物品的重量\(a_{i}\)進行排序;
2. 顯然, 答案貢獻必為 \((a_{i}-a_{i-1})^{2}\) 的形式,即相鄰兩數的平方差;
3. 但此時,貪心思想到此結束,因為我們注意到,有一種情況可能存在;
!:我們選擇 最小的 \(a_{i}-a_{i-1}\) 後,導致 \(a_{i+1}-a_{i-2}\) 及 所有其他答案貢獻 極大,造成答案非最優
**4. **考慮動態規劃,設 \(f[i][j]\) 表示 前\(i\)個物品 選了\(j\)對搬走 時的最優答案,
\(f[i][j]\)可由兩個狀態轉移過來:
(1)選擇搬走\(a_{i}、a_{i-1}\),此時\(f[i][j]=f[i-2][j-1]+(a_{i}-a_{i-1})^2\)
(2)選擇不搬\(a_{i}\),此時\(f[i][j]=f[i-1][j]\)
有狀態轉移方程
$$f[i][j]=min(f[i-2][j-1]+(a_{i}-a{i-1})^2,f[i-1][j])$$
-
TAG:貪心;動態規劃
G.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<climits>
using namespace std;
int n,m,a[2005],f[2005][2005];
int main(){
while(scanf("%d %d",&n,&m)&&n!=0){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=0;i<=n;++i) f[i][0]=0;
//初始化可轉移的合法狀態,即 一對都不選 的狀態
for(int i=2;i<=n;++i)
for(int j=1;j<=min(m,i/2);++j)
//考慮邊界,當選擇到i時,最多可以選 i/2 對物品,而題目同時也限制最多選m對物品
f[i][j]=min(f[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),f[i-1][j]);
printf("%d\n",f[n][m]);
}
return 0;
}
H
-
PZ's solution:
1.根據題意,直接考慮 (。・∀・)ノ゙線段樹維護區間最小值;
2.區間操作 即可視為 區間減 操作;
其他做法請大家自己探索吧,我會將標程做法也貼在下面
-
TAG:線段樹;差分;二分答案
H.cpp
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define mid (l+r>>1)
#define lo o<<1
#define ro o<<1|1
#define N 1000005
int minx[N<<2],lzy[N<<2],n,m,k,t1,t2,ans;
bool f;
void build(int l,int r,int o){
if(l==r){ scanf("%d",&minx[o]); return; }
build(l,mid,lo); build(mid+1,r,ro);
minx[o]=min(minx[lo],minx[ro]);
}
void pushdown(int o){
minx[lo]-=lzy[o];
minx[ro]-=lzy[o];
lzy[lo]+=lzy[o];
lzy[ro]+=lzy[o];
lzy[o]=0;
}
void updata(int l,int r,int L,int R,int k,int o){
if(lzy[o]) pushdown(o);
if(f) return;
if(L<=l&&r<=R){
if(minx[o]<k){ f=1; return; }
minx[o]-=k;
lzy[o]+=k;
return;
}
if(L<=mid) updata(l,mid,L,R,k,lo);
if(R>mid) updata(mid+1,r,L,R,k,ro);
minx[o]=min(minx[lo],minx[ro]);
}
int main(){
scanf("%d %d",&n,&m);
build(1,n,1);
for(int i=1;i<=m;++i){
scanf("%d %d %d",&k,&t1,&t2);
if(!f){
updata(1,n,t1,t2,k,1);
if(f) ans=i;
}
}
printf("%d",ans);
return 0;
}
std.cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+50;
using ll = long long;
int n,m;
ll a[N],room[N],sum[N],t1[N],t2[N];
bool check(int mid)
{
memset(sum,0,sizeof(sum));
for (int i=1;i<=mid;i++)
{
sum[t1[i]]-=room[i];
sum[t2[i]+1]+=room[i];
}
int cnt=0;
for (int i=1;i<=m;i++)
{
cnt+=sum[i];
if (a[i]+cnt < 0) return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for (int i = 1; i <= m; i++)
scanf("%lld%lld%lld", &room[i],&t1[i],&t2[i]);
int l=1, r=m+1;
while(l < r)
{
int mid = (l + r) >> 1;
if (check(mid)) l = mid+1;
else r = mid;
}
if (l == m+1) {printf("0\n");return 0;}
printf("%d\n",l);
return 0;
}
吐槽總結評價
1.這次校賽難度不難,共\(8\)道題,有\(5\)道簽到題(在我看來,只要能第一眼看出 做法和細節 的題,都算簽到題(* ̄3 ̄)╭);
2.自己做題的過程中,F題沒有注意細節,G題一路貪心到底一直錯,H題因為build(1,1,n)
的粗心錯誤一直錯,發現自己也是菜的要死。
3.這次比賽的學長我只認識一位 ssw
,畢竟自己剛來到這裡,讓我們第二次校賽的題解見,到時候就有自己賽場感受了!