CCPC-Wannafly Winter Camp Day1 (Div2, onsite)補題總結
A:unsolved
B:DP
資料範圍非常的小,我們可以首先想到用動態規劃來思考這道題,我們發現每個位置的糖果的數量都可以從上下左右和自己5個狀態轉移過來,也就是
dp[i][j-1][k-1] -> dp[i][j][k]
dp[i][j+1][k-1] ->dp[i][j][k]
dp[i-1][j][k-1] ->dp[i][j][k]
dp[i+1][j][k-1] ->dp[i][j][k]
dp[i][j][k-1] ->dp[i][j][k]
我們列舉每一秒所有位置的狀態,然後從前一秒向下一秒狀態轉移就行了
狀態轉移方程 dp[i][j][k]=max({dp[i-1][j][k-1],dp[i+1][j][k-1],dp[i][j-1][k-1],dp[i][j+1][k-1],dp[i][j][k-1]})+k%T[i]j[j]==0?1:0;
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f const int maxn=1e1+9; int T[maxn][maxn]; int dp[maxn][maxn][10209]; int main(){ int i,j,k,n,m,c; cin>>n>>m>>c; for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ cin>>T[i][j]; } } memset(dp,-inf,sizeof(dp)); int edx,edy,stx,sty; cin>>stx>>sty>>edx>>edy; dp[stx][sty][0]=0; for(k=1;k<=10200;k++){ for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ dp[i][j][k]=max({dp[i][j-1][k-1],dp[i-1][j][k-1],dp[i+1][j][k-1],dp[i][j+1][k-1],dp[i][j][k-1] })+(k%T[i][j]==0?1:0); if(i==edx&&j==edy){ if(dp[i][j][k]>=c){ cout<<k<<endl; return 0; } } } } } }
C:暴力
猜一個結論,任意兩個數都可各拆分為2個數,並且找到一種組合方式使得這4個數兩兩配對的最大公約數為1(也就是互質),然後其中一對數可以在2-5中全部找到。。。。。
我也不知道怎麼證明上述結論的正確性,反正就是這樣暴力找能過,如果有能證明這個結論的正確性的大佬請務必教教我!
所有大於5的數,除了5,6,7,8,所有的數都可以通過減去2、3、4、5其中的一個數得到一個不是2、3、4、5中任何一個數的倍數的數,如果A,B這兩個數中不存在5,6,7,8中的任意1個的話,那麼由於且減去之後得到的數不是2,3,4,5其中任何一個數的公倍數,所以必定互質,如果存在且僅存在一個的話,如果直接互質則不拆,否則拆成2,3,4,5中任意一個數的倍數即可,如果A,B兩個數中存在5,6,7,8中2個的的話,首先(5,6)(5,7),(5,8),(6,7),(7,8)他們本身不需要拆就已經互質,我們不需要去管,然後6,8這兩個數我們可以拆成2,4和3,5,這樣這幾個數內部互相組合的解就都能出來了。
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f const int maxn=1e5+9; #define ll long long ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); } int main(){ ios::sync_with_stdio(0); cin.tie(0); ll i,j,k,n,t,a,b; cin>>t; while(t--){ cin>>a>>b; if(gcd(a,b)==1){ cout<<1<<endl; cout<<a<<' '<<b<<endl; } else{ cout<<2<<endl; for(i=2;i<=5;i++){ for(j=2;j<=5;j++){ if(gcd(i,j)==1&&gcd(a-i,b-j)==1){ cout<<i<<' '<<j<<endl; cout<<a-i<<' '<<b-j<<endl; goto f; } } } } f:; } }
E:樹形dp
首先我們能發現連完邊之後我們得到的是一片森林,也就是我們會得到若干棵樹,我們可以通過新加一個0號節點作為根節點將所有樹連起來成為一棵樹
怎麼連線呢?我們通過並查集加邊構成每一棵樹,最後找祖先節點與0號節點連線起來。
狀態轉移方程式:dp[u][ 1]+=max(dp[v][ 1]-d[min(u,v)],dp[v][0]);
dp[u][ 0]+=max(dp[v][ 1],dp[v][0]);
dp[u][1]代表選取u號節點,dp[u][0]代表不選取u號節點
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn=1e3+20; int d[maxn],f[maxn],dp[maxn][2],p[maxn]; vector<int>e[maxn]; int find(int v){ if(p[v]==0)return v; p[v]=find(p[v]); return p[v]; } void merge(int u,int v){ int t1,t2; t1=find(u); t2=find(v); if(t1!=t2){ p[t2]=t1; } } void dfs(int u,int pre){ dp[u][1]=f[u]; for(int i=0;i<e[u].size();i++){ int v=e[u][i]; if(e[u][i]==pre) continue; dfs(v,u); dp[u][1]+=max(dp[v][1]-d[min(u,v)],dp[v][0]); dp[u][0]+=max(dp[v][1],dp[v][0]); } } int main(){ int i,j,k,n; cin>>n; for(i=1;i<=n;i++){ cin>>f[i]; } for(i=1;i<=n;i++){ cin>>d[i]; } for(i=2;i<=n;i++){ if(i&1){ e[i].push_back(3*i+1); e[3*i+1].push_back(i); merge(i,3*i+1); } else{ e[i].push_back(i/2); e[i/2].push_back(i); merge(i,i/2); } } for(i=1;i<=n;i++){ if(p[i]==0){ e[i].push_back(0); e[0].push_back(i); } } dfs(0,0); cout<<dp[0][0]<<endl; }
F:最短路
這道題題意我理解了挺久。
首先我們能爬的最高的山的高度就是第一座山的高度加初始體力值,我們把所有高於這個高度的山都削成這個高度再儲存下砍山的花費,跑一遍最短路就行了
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f const long long maxn=2e5+9; long long dis[maxn],a[maxn],head[maxn],b[maxn],vis[maxn]; struct node{ long long to,next,w; }edge[maxn*2]; long long cnt=0; void add(long long u,long long v,long long z){ edge[cnt].next=head[u]; edge[cnt].to=v; edge[cnt].w=z; head[u]=cnt++; } typedef pair<long long,long long>pr; long long flag=0; void djk(long long v){ dis[v]=0; priority_queue<pr,vector<pr>,greater<pr> >q; q.push(pr(dis[v],v)); while(!q.empty()){ pr p=q.top(); q.pop(); long long u=p.second; if(vis[u])continue; vis[u]=1; //if(dis[u]<p.first)continue; dis[u]=p.first; for(long long i=head[u];i!=-1;i=edge[i].next){ node e=edge[i]; if(vis[e.to])continue; if(e.w+dis[u]+b[u]<dis[e.to]){ dis[e.to]=e.w+dis[u]+b[u]; q.push(pr(dis[e.to],e.to)); } } } } int main(){ ios::sync_with_stdio(0); cin.tie(0); long long i,j,k,n,m; cin>>n>>m>>k; for(long long i=1;i<=n;i++){ cin>>a[i]; } memset(head,-1,sizeof(head)); for(long long i=1;i<=m;i++){ long long x,y,z; cin>>x>>y>>z; add(x,y,z); add(y,x,z); } long long num=a[1]+k; memset(dis,inf,sizeof(dis)); for(i=2;i<=n;i++){ if(a[i]>num){ b[i]=(a[i]-num)*(a[i]-num); a[i]=num; } } djk(1); cout<<dis[n]+b[n]<<endl; }
J:貪心
這個題剛開始看到的時候覺得是每次選取最便宜的寶物,不過這樣選的話其實並不能得到最優解,比如我如果從擁有最多寶物的人那買寶物的話,那麼實際上是他少了一個寶物,我多了一個寶物,只要這個寶物的價值比最便宜的兩個寶物價值之和小的話,那麼這個就更優,不過這個太難維護了。所以不如我們換個思路,列舉我比獲勝時寶物的個數,對於所有擁有寶物數大於這個的,就買下他的寶物使其寶物數嚴格小於我獲勝時的寶物數,如果買完之後自己所擁有的寶物數還沒有到達我預定的獲勝時擁有的寶物數,則按寶物的價值從小到大購買寶物使得自己達到預定的寶物數
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn=1e3+9; struct node{ ll val,pos; }; bool cmp(node a,node b){ return a.val<b.val; } int vis[maxn]; int main(){ ios::sync_with_stdio(0); cin.tie(0); int i,j,k,n,m; vector<node>v1[maxn],v2; cin>>n>>m; for(i=1;i<=m;i++){ ll id,price; cin>>price>>id; v1[id].push_back(node{price,i}); v2.push_back(node{price,i}); } sort(v2.begin(),v2.end(),cmp); for(i=1;i<=n;i++)sort(v1[i].begin(),v1[i].end(),cmp); long long ans=1e18; for(k=1;k<=m;k++){ memset(vis,0,sizeof(vis)); long long res=0,cnt=0; for(i=1;i<=n;i++){ if(v1[i].size()>=k){ for(j=0;j<=v1[i].size()-k;j++){ res+=v1[i][j].val; vis[v1[i][j].pos]=1; cnt++; } } } for(i=0;i<v2.size();i++){ if(cnt>=k)break; if(!vis[v2[i].pos]){ res+=v2[i].val; cnt++; } } ans=min(ans,res); } cout<<ans<<endl; }
I:DP
掃一遍,每個位置往前記錄中轉點,往前遞推就行了
程式碼:
#include<bits/stdc++.h> using namespace std; #define inf 0x3f3f3f3f const int maxn=2e3+9; #define ll long long ll dp[maxn],p[maxn]; const int mod =1e9+7; int main(){ int i,j,k,n; cin>>n; for(i=1;i<=n;i++){ cin>>p[i]; } ll ans=0; for(i=1;i<=n;i++)dp[i]=1; for(i=3;i<=n;i++){ k=0; for(j=i-1;j>=1;j--){ if(p[j]<p[i])k++; else{ dp[i]=(dp[i]+dp[j]*k)%mod; } } } for(i=3;i<=n;i++){ ans=(ans+dp[i]-1)%mod; } cout<<ans<<endl; }