G、毕业生的纪念礼物
theme:输入n表示纪念品数目,接下来n个数代表第i个数的类别,给每个人发3个不同类别的纪念品,问最多能给几个人发?1≤n≤100000,1≤r[i]≤1e9
solution:首先由贪心,每次应该选择数量最多的三个类别,这样就可以保证没一状态下都是总种类最多状态。注意选出三个后要重排,所以我们用优先队列存,每次取出三个-1,若值不为0,则再存入优先队列。
#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
map<int,int>m;
map<int,int>::iterator it;
priority_queue<int>q;
int main()
{
int n;
cin>>n;
far(i,0,n)
{
int x;
scanf("%d",&x);
m[x]++;
}
int ans=0;
for(it=m.begin();it!=m.end();++it)
q.push(it->second);
while(q.size()>=3)
{
++ans;
int x1=q.top()-1;
q.pop();
int x2=q.top()-1;
q.pop();
int x3=q.top()-1;
q.pop();
if(x1)q.push(x1);
if(x2)q.push(x2);
if(x3)q.push(x3);
}
cout<<ans<<"\n";
}
J、你的粪坑v2
theme:给定n*n的矩阵地图,每个位置上有a[i]的shi.开始时位于(1,1),出口在(n,n)。每次可以选择向下或向右移动,问最少碰到多少shi能到达出口。其中没到达一个位置会碰到该位置上的shi,开始时可任意选择一个方向,之后每改变一个方向,会累积地依次受到1,2,4,8,16...的shi攻击。
solution:用dp[i][j][k][d]表示通过方向d,共改变k次方向,到达位置(i,j)所需最小代价。其中d=0表示从左边过来,d=1表示从右边过来。由于对每个位置要么从左要么从右边来,则对每一个(i,j)循环k的递推式为
dp[i][j][k][0] = min( dp[i][j-1][k][0] , dp[i-1][j][k-1][1] + (1<<(k-1)) ) + a[i][j]
dp[i][j][k][1] = min( dp[i-1][j][k][1] , dp[i][j-1][k-1][0] + (1<<(k-1)) ) + a[i][j]
最终遍历k找到最小值即可:
k :0->15 : ans = min( ans , min( dp[n][n][k][0] , dp[n][n][k][1] ) )
#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[200][200];
int dp[200][200][20][2];//最后一维为0:L,1:R
void getDp(int n)
{
dp[1][1][0][1]=dp[1][1][0][0]=a[1][1];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
if(i==1&&j==1)
continue;
dp[i][j][0][0]=dp[i][j-1][0][0]+a[i][j];
dp[i][j][0][1]=dp[i-1][j][0][1]+a[i][j];
for(int k=1;k<=15;++k)
{
dp[i][j][k][0]=min(dp[i][j-1][k][0],dp[i-1][j][k-1][1]+(1<<(k-1)))+a[i][j];
dp[i][j][k][1]=min(dp[i-1][j][k][1],dp[i][j-1][k-1][0]+(1<<(k-1)))+a[i][j];
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
scanf("%d",&n);
far(i,1,n+1)
far(j,1,n+1)
scanf("%d",&a[i][j]);
memset(dp,0x3f,sizeof(dp));
getDp(n);
int ans=0x3f3f3f3f;
for(int i=0;i<=15;++i)
ans=min(ans,min(dp[n][n][i][0],dp[n][n][i][1]));
printf("%d\n",ans);
}
}
D、B题
theme:给定含有n个节点,n条有向边即将这条有向边反向的代价,问最少需要多少代价可以将这个图变成强连通图。所有值都<=100
solution:强连通图表示只一个强连通分量,所以不用tarjan,直接dfs即可。又n个点n条边为环,则每个节点与两个其它节点相连,由贪心,若从第一个节点出发按环顺时针遍历,若可以到达,则无需代价直接到达下一个节点,若不能到达,则加上代价反向到达。再次遍历到起始节点结束。还有一种情况就是反向逆时针遍历代价最小,可以直接用总的代价-正向代价。
#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[110],b[110],c[110],cost=0,n;
void dfs(int v,int idx)
{
if(v==a[0])
return;
far(i,0,n)
if(idx!=i)
{
if(a[i]==v)
dfs(b[i],i);
else if(b[i]==v)
{
cost+=c[i];
dfs(a[i],i);
}
}
}
int main()
{
while(~scanf("%d",&n))
{
int sum=0;
cost=0;
far(i,0,n)
scanf("%d%d%d",&a[i],&b[i],&c[i]),sum+=c[i];
dfs(b[0],0);
printf("%d\n",min(cost,sum-cost));
}
}
H、毕业生的序列游戏
theme:给定n