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;
}

猜你喜欢

转载自www.cnblogs.com/zookkk/p/10405870.html