演算法初探
內容:
-
APCS題解
-
APCS介紹
-
演算法介紹
APCS題解
2021/11/07
掃過整個陣列,把0變成相鄰最低
(不會的話去複習語法
AC code
by kumokunn
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;cin>>n;
int ans=0;
int arr[n];for(int i=0;i<n;i++)cin>>arr[i];
for(int i=0;i<n;i++){
if(arr[i]==0){
int tmp=101;
if(i>0)tmp=min(tmp,arr[i-1]);
if(i<n-1)tmp=min(tmp,arr[i+1]);
ans+=tmp;
}
}
cout<<ans<<endl;
return 0;
}
煩躁實作題
紀錄每一格有沒有橫線、縱線
做好細節,閱讀不要燒雞
AC code
by yennnn
#include <iostream>
using namespace std;
int xline[101][101], yline[101][101];
/* 0: blank, -1: stake, 1 : line*/
/* xline: horizontal line, yline: vertical line*/
int main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int m, n, h, mx = -1, cnt = 0;
cin >> m >> n >> h;
for (int i = 0; i <= m; i++) {
fill(xline[i], xline[i] + n + 1, 0);
fill(yline[i], yline[i] + n + 1, 0);
}
while (h--) {
int r, c, t;
cin >> r >> c >> t;
if (t == 0) {
int b = -1;
if (xline[r][c] <= 0) {
// not on the xline
b = -1;
// left
for (int i = c - 1; i >= 0; i--) {
if (xline[r][i] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b + 1; i < c; i++) {
xline[r][i] = 1;
}
}
b = -1;
// right
for (int i = c + 1; i < n; i++) {
if (xline[r][i] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b - 1; i > c; i--) {
xline[r][i] = 1;
}
}
}
if (yline[r][c] <= 0) {
// not on the yline
// up
b = -1;
for (int i = r - 1; i >= 0; i--) {
if (yline[i][c] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b + 1; i < r; i++) {
yline[i][c] = 1;
}
}
b = -1;
// down
for (int i = r + 1; i < m; i++) {
if (yline[i][c] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b - 1; i > r; i--) {
yline[i][c] = 1;
}
}
}
xline[r][c] = -1;
yline[r][c] = -1;
cnt = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (xline[i][j] != 0 || yline[i][j] != 0) {
cnt++;
}
}
}
mx = max(mx, cnt);
} else {
int b = -1;
xline[r][c] = 0;
yline[r][c] = 0;
// up
for (int i = r - 1; i >= 0; i--) {
if (yline[i][c] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b + 1; i < r; i++) {
yline[i][c] = 0;
}
}
b = -1;
// down
for (int i = r + 1; i < m; i++) {
if (yline[i][c] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b - 1; i > r; i--) {
yline[i][c] = 0;
}
}
b = -1;
// left
for (int i = c - 1; i >= 0; i--) {
if (xline[r][i] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b + 1; i < c; i++) {
xline[r][i] = 0;
}
}
b = -1;
// right
for (int i = c + 1; i < n; i++) {
if (xline[r][i] == -1) {
b = i;
break;
}
}
if (b != -1) {
for (int i = b - 1; i > c; i--) {
xline[r][i] = 0;
}
}
cnt = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (xline[i][j] != 0 || yline[i][j] != 0) {
cnt++;
}
}
}
mx = max(mx, cnt);
}
}
cout << mx << '\n' << cnt << '\n';
return 0;
}
目標:
-
知道每個位置的工作量
-
讓工作越快的機器在工作越多的位置
差分
\(a_i項的差分為a_{i}和a_{i-1}的差\)
令為\(b_i\)為\(a_i\)的差分
即\(b_i=a_{i}-a_{i-1}\)
性質:\(\sum_{k=1}^n b_i = a_i\)
差分
把區間\(2到5加上3\)
在差分相當於
在2加3
在6減3
1 2 3 4 5 6 7 8 9 10
+3
-3
算每個位置的工作量
就是將線段拆成在差分上的一個加一個減
最後再做前綴和
工作量多的位置
要放效率高的機器
若有兩個位置工作量\(a_i、a_j\)跟
兩個機器做一份工作花\(b_i、b_j\)時間
且\(a_i<a_j\),\(b_i<b_j\)
則有\(b_j*a_i+b_i*a_j>a_i*b_i+a_j*b_j\)
(移項後可證明
AC code(變數名稱不良示範
by kumokunn
#include<bits/stdc++.h>
#define ll long long int
using namespace std;
ll OAO[200002]={},QAQ[200001]={};
int main(){
int n,m;cin>>n>>m;
while(m--){
int a,b,c;cin>>a>>b>>c;
OAO[a]+=c;
OAO[b+1]-=c;
}
for(int i=1;i<=n;i++){
OAO[i]+=OAO[i-1];
}
for(int i=1;i<=n;i++){
cin>>QAQ[i];
}
sort(OAO+1,OAO+n+1);
sort(QAQ+1,QAQ+n+1);
ll ans=0;
for(int i=1;i<=n;i++){
ans+=QAQ[i]*OAO[n-i+1];
}
cout<<ans<<endl;
return 0;
}
先備知識:二分圖判斷
方法1.BFS對點做奇偶判斷
方法2.DSU把邊視為點集的合併
奇偶判斷:畫顏色!
即相鄰節點奇偶不同(顏色不同
有相鄰節點奇偶不同則不合法
點集
一開始每個點都是一個點集
每一個邊要讓兩個點
和與對方相鄰的點在同一個點集
若有兩個相鄰點在同一點集則不合法
解法一、二分搜
每次考慮前綴的調查員和組長的資料
用BFS判斷是否為二分圖
合法調查員
不合法調查員
1 2 3 4 5 6
\(f(x)\):到x的前綴是否為二分圖
f(x) | 1 | 1 | 1 | 0 | 0 | 0 |
---|
解法二、DSU
分別用每個調查員的資料
對每個邊把點集和與對方相鄰的點集合併
若有相鄰點在同一點集則不合法
每次把DSU還原成只有組長資料的狀態
AC code(並查集
by kumokunn
#include<bits/stdc++.h>
#define pb push_back
#define pf push_front
#define mp make_pair
#define F first
#define S second
#define pq priority_queue
#define ll long long int
#define pii pair<int,int>
#define endl '\n'
#define Orz ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define MAXN 100000
using namespace std;
int arr[20100],vis[20100]={};
vector<int> dis[20100],ans;
int find(int pos){
if(arr[pos]!=pos){
arr[pos]=find(arr[pos]);
return arr[pos];
}else return pos;
}
void unnion(int a,int b){
if(find(a)==find(b))return;
if(find(a)>find(b))swap(a,b);
arr[find(a)]=find(b);
return;
}
void dfs(int pos,int pre){
vis[pos]=1;
for(auto i:dis[pos]){
if(vis[i]==0){
if(pre!=-1)unnion(pre,i);
dfs(i,pos);
}
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b;cin>>a>>b;
dis[a].pb(b);
dis[b].pb(a);
}
for(int i=0;i<n;i++)arr[i]=i;
for(int i=0;i<n;i++){
if(vis[i]==0){
dfs(i,-1);
}
}
int p,k;cin>>p>>k;
for(int j=0;j<p;j++){
set<int> se;
int flag=1;
for(int i=0;i<k;i++){
int a,b;cin>>a>>b;
if(find(a)==find(b)){
flag=0;
}else{
if(dis[a].size()>0){
int tmp1=b,tmp2=dis[a][0];
if(find(tmp1)>find(tmp2))swap(tmp1,tmp2);
se.insert(find(tmp1));
unnion(tmp1,tmp2);
}
if(dis[b].size()>0){
int tmp1=a,tmp2=dis[b][0];
if(find(tmp1)>find(tmp2))swap(tmp1,tmp2);
se.insert(find(tmp1));
unnion(tmp1,tmp2);
}
}
}
if(!flag){
ans.pb(j+1);
for(auto z:se)arr[z]=z;
}
}
for(auto i:ans)cout<<i<<endl;
return 0;
}
AC code(並查集
by kennychenfs
#include <iostream>
#include <vector>
#define Hirasawa_Yui_My_Wife ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
using namespace std;
const int N=2e4+10,P=1e4+10;
int n,m,p,k;
vector<int> G[N];
int color[N],lead[N],f[N],sz[N];//lead[i]=lead of color i
int oc(int c){return (c%2)?c+1:c-1;}
vector<int> ch;
int fa(int u){//zip = if 路徑壓縮
if(f[u]==u)return u;
ch.pb(u);
return f[u]=fa(f[u]);
}
void dfs(int u,int c){
color[u]=c;
for(int i:G[u])if(!color[i])dfs(i,oc(c));
}
int main(){
Hirasawa_Yui_My_Wife
cin>>n>>m;
int u,v;
while(m--){
cin>>u>>v;
++u;++v;
G[u].pb(v);
G[v].pb(u);
}
int now=1;
for(int i=1;i<=n;++i){
if(!color[i])dfs(i,now),now+=2;
}
cin>>p>>k;
for(int i=1;i<=now;++i)f[i]=i;
vector<int> wrong;
for(int o=1;o<=p;++o){
now=0;
bool flag=0;
ch.clear();
for(int i=0;i<k;++i){
cin>>u>>v;
++u;++v;
if(flag)continue;
int cu=color[u],cv=color[v];
int x=fa(cu),y=fa(cv);
if(x==y){wrong.pb(o);flag=1;continue;}
f[x]=fa(oc(cv));
f[y]=fa(oc(cu));
ch.pb(x);
ch.pb(y);
}
for(int i:ch)f[i]=i;
}
for(int i:wrong)cout<<i<<'\n';
}
AC code(二分搜
by thanksone
#include <bits/stdc++.h>
#define pb push_back
#define mid (l + r) / 2
using namespace std;
struct e{
int u, v;
};
int n;
array<bool, 10004> WA;
array<int, 20004> vis;
array<vector<e>, 10004> E;
array<vector<int>, 20004> G;
bool dfs(int u, int t){
if(vis[u]) return 1;
bool ans = 1;
vis[u] = t;
for(int v : G[u]){
if(vis[v] == t) return 0;
ans &= dfs(v, 3 - t);
}
return ans;
}
bool check(int p){
bool ans = 1;
for(int i = 0; i < n; i++){
G[i].clear();
vis[i] = 0;
}
for(int i = 0; i <= p; i++){
for(auto [u, v] : E[i]){
G[u].pb(v);
G[v].pb(u);
}
}
for(int i = 0; i < n; i++){
ans &= dfs(i, 1);
}
return ans;
}
void BS(int l, int r){
if(check(r)) return;
while(l != r){
if(check(mid)) l = mid + 1;
else r = mid;
}
WA[l] = 1;
E[l].clear();
}
signed main(){
int m, p, k, a, b;
cin >> n >> m;
while(m--){
cin >> a >> b;
E[0].pb({a, b});
}
cin >> p >> k;
for(int i = 1; i <= p; i++){
for(int j = 0; j < k; j++){
cin >> a >> b;
E[i].pb({a, b});
}
}
for(int i = 0; i < 3; i++){
BS(0, p);
}
for(int i = 1; i <= p; i++){
if(WA[i]) cout << i << "\n";
}
return 0;
}
APCS介紹
APCS為Advanced Placement Computer Science的英文縮寫
是指「大學程式設計先修檢測」
其檢測模式乃參考美國大學先修課程
(Advanced Placement,AP)
與各大學合作命題,並確定檢定用題目經過信效度考驗
以確保檢定結果之公信力
用途
- 檢測能力
- 升學
- 取得TOI初選資格
APCS組
臺灣大學 | 3 |
清華大學 | 3 |
陽明交通大學 | 3 |
成功大學 | 3 |
準備策略
- 熟悉語法(練習一些題目
- 學習演算法
如何學習演算法
強強學長的slides
有了資源,還需要練習
學習演算法:
看講義or聽課
寫講義的題or找題寫
演算法介紹
演算法:解決問題的方法
資訊中的演算法,指的是可以經過
有限個步驟解決問題的一種方法
演算法也可以是一種邏輯思維
栗子
問題:把蘋果做成蘋果汁
-
清洗蘋果
-
將蘋果削皮、去籽
-
將經過步驟(2.)處理的蘋果放入果汁機
-
在果汁機中加入一定比例的水
-
按下果汁機啟動按鈕
-
將果汁機裡面的蘋果汁倒入玻璃杯中
演算法:
四種常見演算法的思維
-
枚舉
-
貪婪(greedy
-
動態規劃(DP
-
分治(DC
枚舉
把候選解都試過一遍
在數列中有沒有兩個數合為10
1,2,3,4,5,6,7,8,9,10
每次抓兩個數,看相加和是不是10
1+2,1+3,1+4,1+5.....2+3,2+4...
栗子
貪婪
不拿白不拿
栗子
吃慢得先吃一定比較好
動態規劃
做過的子問題就不要再做一次
栗子
利用紀錄來避免重複計算子問題
費撥那契數列第N項
分治
把一個問題分成幾個小問題
枚舉
有可能的都試一遍
例題
給你一個數列\(a_1,a_2...a_n\)
是否有兩個數和為\(K\)
用兩個for枚舉
greedy
不拿白不拿
\(\rightarrow\)照這個策略一定是最優解(之一)
例題
greedy策略
吃越慢的越早吃
真的對嗎?
簡單證明A部分:
因為老闆會一直煮菜
不管順序如何,最後一道煮完的時間固定
簡單證明B部分:
若有兩個人(A紅色、B藍色)
的吃飯時間如下
交換後會有更好的解
DP
曾經有人說過
不能greedy就DP
特性
重複子問題
相同的一個子問題,需要多次查詢
最佳子結構
可以透過這些子問題得到這個問題的最佳解
無後效性
已經完成的子問題不會因後續的子問題
而非最佳解
實作方法
top_down(迴圈) bottom_up(函數)
吠事數列
扣的
int main(){
int arr[48763];
arr[0]=1,arr[1]=1;
for(int i=2;i<=10;i++)arr[i]=arr[i-1]+arr[i-2];
return 0 ;
}
爬個樓梯
justforfun時常有腿太長的困擾
爬樓梯常常要決定一次要爬幾階(1~3階)
給你他要爬幾階樓梯,請問他有幾種爬法
DP步驟
- 設定狀態:走到這一階的方法數
- 設定轉移式:\(a_i=a_{i-1}+a_{i-2}+a_{i-3}\)
- 設定基礎:\(a_0=1,a_1=1,a_2=2\)
扣的
int main(){
int arr[48763];
int n;cin>>n;
arr[0]=1,arr[1]=1;arr[2]=2;
for(int i=3;i<=n;i++)arr[i]=arr[i-1]+arr[i-2]+arr[i-3];
cout<<arr[n]<<endl;
return 0 ;
}
小練習
DP其實有很多很多經典題
應該都會講ㄅ(?
- 背包
- 區間DP
- 最大子陣列和
- LIS
- edit distance
- 切正方形
分治
by yennnn
演算法初探
By linki1010111
演算法初探
- 768