集训3
我直接拍手称寄。
\(\textbf{“我以为T1,T3能场切”}\)
T1:Watching fire is fun.
只有10pts确实挺fun的。
一眼DP,二眼贪心,三眼DP。然后用错的DP转移方程搞了10pts,乐。
一开始打算贪心是因为$ n $给到了\(1500000\),我寻思搞个DP怎么着也得要个\(O(n^2m)\)吧,所以就跳过了,后来还是老老实实写的DP。于是\(Wrong Answer\)力。昨天刚考了斜率优化,今天连个单调队列的毛都没想出来(不过我就算知道要用单调队列,转移方程也不会推,寄)。太惨了。
用\(f[i][j]\)表示第\(i\)次烟花位置为\(j\)的最大幸福度,那么转移方程就是:
然鹅我们需要小调一下。
-
空间复杂度问题,由于需要开\(long long\),又观察到每一个\(i\)都要从前一个状态转移过来,所以我们可以使用滚动数组减小空间上的开支。
-
时间复杂度问题,我们可以对\(f[i-1][k]\)进行单调,维护一个该值递增的队列,需要取出决策时及时排除位置已经不符合条件的选项。
代码:
#include <bits/stdc++.h>
#define int long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=330;
const int inf=9999999999999999;
int n,m,d;
int Ai[maxm],Bi[maxm],Ti[maxm];
int f[maxn],dp[maxn];
int q[maxn];
int ans=-inf;
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
signed main(){
freopen("fire.in","r",stdin);
freopen("fire.out","w",stdout);
n=read();m=read();d=read();
for(Reg int i=1;i<=m;++i) Ai[i]=read(),Bi[i]=read(),Ti[i]=read();
for(int i=1;i<=m;++i){
memcpy(dp,f,sizeof(f));
//滚动数组
if(Ti[i]==Ti[i-1]){
for(int j=1;j<=n;++j)f[j]=dp[j]+Bi[i]-abs(Ai[i]-j);
continue;
}
int l=1,r=0,movs=(Ti[i]-Ti[i-1])*d,k=1;
for(int j=1;j<=n;++j){
for(k;k<=min(j+movs,n);k++){
while(l<=r&&dp[k]>=dp[q[r]]) r--;
q[++r]=k;
}
while(l<=r&&j-movs>q[l]) l++;
f[j]=dp[q[l]]+Bi[i]-abs(Ai[i]-j);
}
}
for(int i=1;i<=n;++i) ans=max(ans,f[i]);
cout<<ans;
return 0;
}
T2:Performs
唯一一个切了的题,DP方程式贼好想(尼玛又是DP😭👊👊👊),每一个城市每一天的最小花费可以由之前的任一一个城市转移而来,不过需要特殊注意的是起点只能是一城市。循环的问题可以直接暴力解决,毕竟\(K\)不超过\(1000\),或者可以在方程中取模。\(Kamisato_-Ayaka\)巨佬给出了暴搜的好方法。
#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=1100;
const ll inf=999999999;
int n,k;
int fees[15][15][maxm];
int f[15][maxm];
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void solve(){
f[1][0]=0;
for(int i=2;i<=n;++i){
if(!fees[1][i][1]) continue;
f[i][1]=min(f[i][1],f[1][0]+fees[1][i][1]);
//printf("%lld ",f[i][1]);
}
for(int e=2;e<=k;++e){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(e==2&&j==1) continue;
if(i==j) continue;
if(!fees[j][i][e]) continue;
f[i][e]=min(f[i][e],f[j][e-1]+fees[j][i][e]);
}
}
}
if(f[n][k]==inf) printf("0\n");
else printf("%d\n",f[n][k]);
return;
}
inline void xuns(int i,int j,int t){
int segs=2;
while(t<=k){
for(int e=t+1;e<=segs*t;e++) fees[i][j][e]=fees[i][j][e-t];
t=segs*t;segs++;
}
return;
}
inline void inputrs(){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(i==j) continue;
int xujie=read();
for(int t=1;t<=xujie;++t) fees[i][j][t]=read();
xuns(i,j,xujie);
}
}
for(Reg int i=0;i<=n+1;++i)
for(Reg int j=0;j<=k+1;j++)
f[i][j]=inf;
}
int main(){
freopen("perform.in","r",stdin);
freopen("perform.out","w",stdout);
n=read();k=read();
while(n!=0&&k!=0){
memset(fees,0,sizeof(fees));
inputrs();
solve();
n=read();k=read();
}
return 0;
}
T3:枪战 Maf
题意就是一群人互相打手枪(迫真),决定一个开枪顺序,求出最少死亡人数和最多死亡人数。考场上推了半小时写了半小时,获得了\(0pts\)的好成绩。
这道互相打手枪的题仔细一想有不少有意思的性质:
-
死亡人数最多时,活下来的只有入度为\(0\)的人和一个独立环里的某一个人。
-
死亡人数最少时,一个有\(n\)个人的环内活下来的只有\([n/2]\)个人,最多时只有\(1\)个。
-
对于某一条链,我们选择从入度为\(0\)的人开枪,被打死的人就不能再开枪,而被打死的人想打死的人可以开枪。、
-
如果一条链指向了某个环,那么死亡人数最多时这个环一个活口都没有了。
真就乱杀
所以我们先用拓扑排序把所有的链删除,再遍历所有的环,同时维护死亡人数最少和最多时的存活人数(死亡人数太难搞)如果这个环在遍历过程中发现被指向或者存在自环(草),那么死亡人数最多时的存活人数就不再加\(1\),死亡人数最少时是节点数除以2。
之前对某一些性质的理解确实有误差,比如如果一条链指向一个环,那么这个环的最小死亡人数会不会被影响之类的,后来才发现自己多虑了。
感谢\(smtwy\)巨佬让我乱杀改掉了这道题。
代码:
#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std;
const int maxn=1301010,maxm=330;
int n,cnt,head[maxn],axli,inli,tot,in[maxn];
bool sw[maxn],vis[maxn],vis2[maxn];
struct ED{
int next,to;
}e[maxn*2];
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void add(int u,int v){
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
return;
}
inline void finalbt(int u){
int laji=0;
bool fl=0,fl2=0;
//草 终于明白了
while(1){
if(vis[u]) break;
//嘶,刚才想错了
//vis不会出现环与链交叉的情况
//但save一定会有,如果说这个环里有save节点,那么这个环就被指过了
//死亡人数最多时这个环的全部节点都会被打死
//否则只剩1人
vis[u]=1;
u=e[head[u]].to;
laji++;
//环的节点数++
if(sw[u]) fl=1;
}
if(laji>1&&!fl) axli++;
//自环的情况
inli+=laji/2;
return;
}
inline void tope(){
queue<int> q;
for(int i=1;i<=n;++i){
if(!in[i]) q.push(i),inli++,axli++;
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(vis[v]) continue;
vis[v]=1;
int vv=e[head[v]].to;
//此时一定能保证能活下来的都已遍历过
in[vv]--;sw[vv]=1;
//把死亡人数最少时的幸存者加入队列
if(!in[vv]) inli++,q.push(vv);
}
}
//去链找环
for(int i=1;i<=n;++i){
if(in[i]&&!vis[i]) finalbt(i);
}
}
int main(){
freopen("maf.in","r",stdin);
freopen("maf.out","w",stdout);
n=read();
for(Reg int i=1;i<=n;++i){
int aims=read();add(i,aims);
in[aims]++;
}
tope();
printf("%d %d",n-inli,n-axli);
return 0;
}
T4:翻转游戏
无脑输出\(0\)和\(Impossible\)能拿\(50pts\),乐。
真正的暴力搜索,然而咱还是不会(😭)。
来自gtm1514巨佬的代码:
#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=330;
int s[20][20],ans=100,cnt;
int x[20],y[20];
bool fbi,fwi;
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void Reverse(int cz){
int i=x[cz],j=y[cz];
s[i][j]^=1;
if(j>1) s[i][j-1]^=1;
if(j<4) s[i][j+1]^=1;
if(i>1) s[i-1][j]^=1;
if(i<4) s[i+1][j]^=1;
return;
}
inline bool check(){
fbi=fwi=0;
for(int i=1;i<=4;++i){
for(int j=1;j<=4;++j){
if(s[i][j]!=s[1][1]) return false;
}
}
return true;
}
void dfs(int now){
if(check()){
ans=min(ans,cnt);
return;
}
if(now==17) return;
for(int i=0;i<=1;++i){
if(i==1) Reverse(now),cnt++;
dfs(now+1);
if(i==1) Reverse(now),cnt--;
}
return;
}
int main(){
freopen("flip.in","r",stdin);
freopen("flip.out","w",stdout);
for(int i=1;i<=4;++i){
for(int j=1;j<=4;++j){
char ps; scanf(" %c",&ps);
if(ps=='b') s[i][j]=1,fbi=1;
else s[i][j]=0,fwi=1;
}
}
for(int i=1;i<=16;++i){
y[i]=i%4==0?4:i%4;
x[i]=i%4==0?(i/4):(i/4+1);
}
if(!fbi||!fwi) return printf("0"),0;
else{
dfs(1);
if(ans<=16) printf("%d",ans);
else printf("Impossible");
//真得好好练暴搜了
}
return 0;
}
我写完了。

浙公网安备 33010602011771号