A
dfs 思维
和题解的做法略有不同。
每次可以控制所有袋鼠一块移动,移动到洞口或者出界都会淘汰,问对于每个袋鼠,是否存在一个操作序列让它活到最后?
注意到每个人都能移动到所在连通块内的任意位置,所以连通块内的每个人要么都能活到最后,要么都不能。(如果有一个人能活到最后,其他人先走到这个人的位置,然后做同样的操作也能活到最后)。所以我们可以以连通块为单位思考,对于每个联通块,能活到最后的条件是,不存在另一个连通块,是它的超集,或者说能完全包住他。因为如果存在一个超集,这个联通块不管怎么动,超集里的元素总能跟着动,要么都出局,要么都不出局,不能让子集内的人成为活到最后的一个人。
所以问题转化成对于每个联通块,判断是否存在另一个块是他的超集?这里涉及到形状,很难直接判断超集。最后乱搞出来一种方法是,对于每个联通快,任选一个点作为代表(块内每个点都是平等的),然后让这个点在块内做dfsdfsdfs,同时带动其它所有人一起移动,也就是其它所有人的移动都要和这个代表完全相同,然后dfsdfsdfs遍历完连通块内所有位置后,看其它所有人是否都被淘汰,如果是就说明这个连通块不存在一个超集,是合法的,把他的大小计入答案。
这里的dfsdfsdfs不仅包含递归进入的移动,还包含回溯的移动。形式化来说就是我们在这个连通块的dfsdfsdfs生成树上进行了一个欧拉序列的访问。如果不包含这个回溯就会wawawa,猜测是因为,回溯的移动在我们实际玩这个游戏的时候也是要做的,并且这部分移动也可能会淘汰一些人。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int dx[4]={1,0,-1,0},dy[4]={0,-1,0,1};
void solve() {
int n,m;
cin>>n>>m;
vector<vector<int>>a(n+1,vector<int>(m+1));
vector<vector<int>>vis(n+1,vector<int>(m+1));
vector<vector<int>>pos(n+1,vector<int>(m+1));
vector<pair<int,int>>p;
vector<int>live;
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char c;
cin>>c;
if(c=='.'){
a[i][j]=1;
p.push_back({i,j});
pos[i][j]=cnt++;
live.push_back(1);
}
}
}
bool ok=0;
int curp;
auto &&dfs=[&](auto &&dfs,int x,int y)->int{
int res=1;
vis[x][y]=1;
int cnt=0;
for(int i=0;i<(int)p.size();i++){
if(i==curp)continue;
if(!live[i]){
cnt++;
}
}
if(cnt==(int)p.size()-1){
ok=1;
}
// vector<int>copy;
// copy=live;
for(int i=0;i<4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(xx<1||xx>n||yy<1||yy>m||!a[xx][yy]||vis[xx][yy])continue;
int j=0;
for(auto &[x,y]:p){
x+=dx[i];
y+=dy[i];
if(live[j]){
if(x<1||x>n||y<1||y>m||!a[x][y]){
live[j]=0;
}
}
j++;
}
res+=dfs(dfs,xx,yy);
for(auto &[x,y]:p){
x-=dx[i];
y-=dy[i];
}
// live=copy;
}
return res;
};
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]&&!vis[i][j]){
ok=0;
curp=pos[i][j];
vector<int>copy;
copy=live;
int sz=dfs(dfs,i,j);
live=copy;
// cout<<i<<' '<<j<<' '<<ok<<' '<<sz<<'\n';
if(ok){
ans+=sz;
}
}
}
}
cout<<ans<<'\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T; cin >> T;
while (T--) {
solve();
}
}
G
背包 贪心
一个背包问题,新增一个条件:可以选不超过kkk个东西,免费获得,不占空间。
朴素的想法是增加一个维度表示使用了多少次免费次数,但这太慢了,完全不可行。可能这个数据,复杂度必须严格O(nW)O(nW)O(nW),多一点都会超。
那么dpdpdp优化就不太可行了,考虑贪心。一个比较自然的想法是,贪心用这个免费次数,要么用在价值高的上面,要么用在重量大的上面。
考虑优先用在重量大的上面,我们把www降序来考虑,但显然不是重量最大的几个直接用就完了,还得选。
这里有个重要的性质,如果wa>wbw_a>w_bwa>wb,不存在wbw_bwb免费选了,waw_awa花钱买的情况。因为1他们俩既然最后都要加入答案,那么交换一下,让重量大的免费,重量小的花钱,肯定不会更劣。那么一定存在一个分界点iii,前面的元素只存在免费买,后面的元素只存在花钱买。那么考虑枚举这个分界点,后面的元素花钱买,就是个朴素dpdpdp,预处理所有后缀的dpdpdp结果;前缀的元素可以至多选kkk个,那么选价值最大的kkk个,这就是维护前缀里的前kkk大,考虑堆维护topktopktopk
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 5005, W = 10005;
int suf[N][W];
struct Item {
int w, v;
bool operator<(const Item &o) {
return w > o.w;
}
} item[N];
const int INF = 1e18;
void solve() {
int n, W, k; cin >> n >> W >> k;
for (int i = 1; i <= n; i++) {
cin >> item[i].w >> item[i].v;
}
suf[n+1][0] = 0;
sort(item+1, item+1+n);
for (int i = n; i >= 1; i--) {
int wi = item[i].w, vi = item[i].v;
for (int j = 0; j <= W; j++) {
if (j >= wi)
suf[i][j] = max(suf[i+1][j], suf[i+1][j-wi] + vi);
else
suf[i][j] = suf[i+1][j];
}
}
priority_queue<int, vector<int>, greater<int>> pq;
int sumv = 0, ans = suf[1][W];
for (int i = 1; i <= n; i++) {
int vi = item[i].v;
pq.push(vi); sumv += vi;
if ((int)pq.size() > k) {
sumv -= pq.top();
pq.pop();
}
ans = max(ans, sumv + suf[i+1][W]);
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1;
while (T--) {
solve();
}
}
L
贪心 模拟
每层分别需要一定量的重量1/21/21/2的东西,电梯载重上限kkk,每次运行电梯的代价是到达的最高层数,每次运行电梯就是从地面装上一些东西,然后运行到某些层,卸货。问让每层的要求都满足的最小代价是多少?kkk是偶数
应该先从高层的需求开始运,因为给高层运的同时,可以额外给低层运,但反过来不行。那么一个朴素贪心就是每次给最高层运一个1/21/21/2,这样的问题是总需求量很大,直接模拟会超时。但总楼层数不多,这种时候就该考虑,一次处理完一层的需求,模拟的复杂度就能降低到O(n)O(n)O(n)级别。
每层的需求既有111也有222,讨论很麻烦。但注意到kkk是偶数,如果我们能把每层的需求的转化成只有222,这个贪心就好做很多了,只需要计算每层至少需要多少趟电梯,最后一趟可能存在空载,记录空载重量,处理下一层时,把下一层的需求减掉这部分空载(可以视为这部分被上一层的最后一次电梯运走了)。
把每一层的需求都转化成偶数,首先每层的222需求不用动,111需求如果是偶数也不用动。如果111需求是奇数,需要从更低层拿一个过来,可以视为把更底层的一个111也在这一层的最后一次电梯运了。优先拿也是奇数的更底层,这样一次可以解决两层的奇数,如果更低层都是偶数了,这次机会就浪费了,也就是这一层最后一班电梯会有111的空载。
转化完之后跑前面提到的贪心即可,每层计算电梯次数时都上取整,因为可能存在某一层需求是奇数,但根据我们上面的分析,多出来的111的空间就只能浪费了,我们把它上取整当成偶数。
void solve(){
int n,k;
cin>>n>>k;
map<int,int>c1,c2;
rep(i,1,n){
int c,w,f;
cin>>c>>w>>f;
if(w==1){
c1[f]+=c;
}
else{
c2[f]+=c;
}
}
vvi t;
for(auto &[k,v]:c1){
t.push_back({k,v});
}
reverse(t.begin(),t.end());
int m=t.size();
for(int i=0,j=0;i<m;i++){
if(t[i][1]%2){
j=i+1;
while(j<m&&t[j][1]%2==0){
j++;
}
if(j<m){
t[i][1]++;
t[j][1]--;
}
}
}
for(auto &p:t){
c2[p[0]]+=(p[1]+1)/2;
}
t.clear();
for(auto &[k,v]:c2){
t.push_back({k,v});
}
reverse(t.begin(),t.end());
int ans=0,space=0;
for(auto &p:t){
int f=p[0],cnt=p[1]*2;
if(cnt>space){
cnt-=space;
space=0;
}
else{
space-=cnt;
cnt=0;
}
if(cnt){
// cout<<f<<' '<<cnt<<'\n';
ans+=(cnt+k-1)/k*f;
int r=cnt%k;
if(r){
space+=k-r;
}
}
}
cout<<ans<<'\n';
}
1800

被折叠的 条评论
为什么被折叠?



