校内集训20180920
$T1:$(Loj2333)
铁路沿线$N$个车站,车分三种:快,慢,次快车。慢车走1个站花$A$单位时间,每站都停;快车走1个站花费$B$单位时间,只在$M$个站停,停靠站输入给出(起点$1$和终点$N$必停);次快车走1个站花费$C$单位时间,一共在$K$个站停,且必须在快车停靠站停,剩下$K-M$个停靠站随意建造。速度$A,B,C$满足$B<C<A$。求如何建造次快车停靠站使得在$T$分钟内能到达的车站尽量多。(所有移动均为单向,$1->N$方向)
(到达:停靠在此站。每个车站单独计数,即统计有几个车站从$1$走到该车站花费$T$分钟以内。)
$N\leq 10^9,M\leq K\leq 3000$。
题解:
第一遍看根本没看懂,$JOI$考的都是读题能力么……
看懂的话应该很快发现由于$B<C<A$,整个图被快车站分成了$M-1$个块。
修建的任意一个次快车站只能影响到它所在的块内在它后面的车站。
(块外的车站可以通过快车走到,并且不影响次快车在该块内的修建(所有快车停靠站都是次快车停靠站)为什么还要走次快车?)
显然,已知速度,时间,每个块内修建$k$个次快车站的贡献是可以计算的。
那么即可得到$O(M\times K^2)$的$dp$,$dp[i][j]$表示处理到第$i$个块,一共修建了$j$个次快车站时能到达的最多车站。
枚举该块内修建了$k$个转移即可。
好像过不了,考虑优化。
容易发现一个块内每放一个车站的贡献值是递减的。($V$不变,$T$减少,$S$便减少)
由此产生一个很重要的推论:一个块内按贡献值从大到小排序依次修建,一定是合法的。
(不会出现一个块内修建第$k$个的贡献比第$k-j$个贡献大的情况,否则你没修建到第$k-j$个怎么修建第$k$个?)
那么既然每个块互不影响,所有块均满足该推论。
于是可以将所有块内修建第$i$个块的贡献值扔到一个优先队列里,每次操作取出队首,在此修建,累加答案,并将其出队。操作$k$次得到答案。
复杂度$O(K\times log(M\times K))$。
这题想了$30min$,标程写了$1h-$,对拍写了$2h+$写挂了,最后直接交标程$A$了,我尼玛……
代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; #define MAXN 3005 #define MAXM 500005 #define INF 0x7fffffff #define ll long long priority_queue<ll> q; ll S[MAXN],sgo[MAXN][MAXN]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int main(){ ll N=read(),M=read(),K=read();K-=M; ll A=read(),B=read(),C=read(),T=read(); for(ll i=1;i<=M;i++) S[i]=read(); sort(S+1,S+1+M); for(ll i=1;i<=M;i++){ if(i==1) continue; //cout<<i<<endl; ll slen=S[i-1]-1;ll stim=T-(slen*B); //cout<<stim<<endl; if(stim<0) break; //cout<<stim<<endl; sgo[i][0]=stim/A; ll sum=sgo[i][0]; //cout<<sum<<":"<<S[i-1]<<":"<<S[i]<<endl; if(sum+S[i-1]>=S[i]){ sgo[i][0]-=(sum+S[i-1]-S[i]+1); sgo[i][0]=max(sgo[i][0],(ll)0); //cout<<i<<":"<<sgo[i][0]<<" "; continue; } //cout<<i<<":"<<sgo[i][0]<<" "; for(ll j=1;j<=K;j++){ if(stim<(sum+1)*C) break; sgo[i][j]=(stim-(sum+1)*C)/A+1; sum+=sgo[i][j]; if(sum+S[i-1]>=S[i]){ sgo[i][j]-=(sum+S[i-1]-S[i]+1); sgo[i][j]=max(sgo[i][j],(ll)0); //cout<<sgo[i][j]<<" "; break; } //cout<<sgo[i][j]<<" "; } //cout<<endl; } ll ans=0; for(ll i=2;i<=M;i++){ if((S[i]-1)*B>T) break; ans++; } //cout<<ans<<endl; for(ll i=1;i<=M;i++){ //cout<<i<<":"<<sgo[i][0]<<endl; ans+=sgo[i][0]; //cout<<ans<<endl; } //cout<<ans<<endl; for(ll i=1;i<=M;i++) for(ll j=1;j<=K;j++) q.push(sgo[i][j]); for(ll i=1;i<=K;i++){ans+=q.top();q.pop();} printf("%lld\n",ans); //cout<<ans<<endl; /* for(ll i=1;i<=M;i++){ if(S[i]==1) continue; ll slen=S[i-1]-1;ll stim=slen*B; ll st=T-stim;ll sgo[0]=st/A,cnt=0; if(st<0) break; for(ll k=0;k<=K;k++) dp[i][k]=dp[i-1][k]+sgo[0]+1; ll num=sgo[0]; if(num>=S[i]-S[i-1]-1) continue; for(ll k=1;k<=K;k++){ sgo[k]=(st-(sgo[i-1]*C))/A; num+=sgo[k],cnt++; if() } for(ll k=1;k<=K;k++){ for(ll l=1;l<=k;l++){ sgo= dp[i][k]=dp[i-1][k]+ } } }*/ return 0; } /* 12 3 4 10 1 2 30 1 11 12 */
T2(Loj2334):
给你一个$N\times M$的矩阵,将其划分成两块使得两块中的极差($max-min$)最大者最小。划分不能出现“突出”或“凹陷”。
原句:对于每一行/列,如果我们将这一行/列单独取出,这一行/列里同省的任意两个区块互相连接。这一行/列内的所有区块可以全部属于一个块。
$N,M\leq 2000$。
题解:
看懂了非常好做,考点依旧是读题。
根据题意第一个国家只可能处在第二个的左上,右上,左下,右下。
那么每个方向二分答案求解即可。
但如果看了上一题的时间花费就知道我已经没时间写这题了……
代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 3005 #define MAXM 500005 #define INF 0x7fffffff #define ll long long int N,M,maxn,minn; int A[MAXN][MAXN]; int tmp[MAXN][MAXN]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } bool check(int x){ int pos=M; for(int i=1;i<=N;i++){ for(int j=1;j<=pos;j++) if(A[i][j]-minn>x) {pos=j-1;break;} for(int j=pos+1;j<=M;j++) if(maxn-A[i][j]>x) return 0; } return 1; } void sswap(){ for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) tmp[j][N-i+1]=A[i][j]; for(int i=1;i<=M;i++) for(int j=1;j<=N;j++) A[i][j]=tmp[i][j]; return; } int main(){ N=read(),M=read(); maxn=-INF,minn=INF; for(int i=1;i<=N;i++) for(int j=1;j<=M;j++){ A[i][j]=read(); maxn=max(maxn,A[i][j]); minn=min(minn,A[i][j]); } int mans=INF; for(int i=1;i<=4;i++){ int l=0,r=maxn-minn,ans=-INF; while(l<=r){ int mid=(l+r)>>1; if(check(mid)) ans=mid,r=mid-1; else l=mid+1; } mans=min(ans,mans); sswap();swap(N,M); } printf("%d\n",mans); return 0; }
T3(Loj2325):
bzoj4832升级版,攻击次数从$50$变为$10^{18}$(炉石传说没有外挂),求期望对$998244353$取模的值。
题解:
弱化版题目没什么问题吧……不管用什么奇怪的顺序转移应该都是能$A$的。
多说一句,如果直接预处理$dp[i][j][k][l]$表示还剩$i$次攻击,剩余$1,2,3$血奴隶主的个数分别为$j,k,l$个,
每次从还剩$i-1$次攻击转移过来(也就是反向建拓扑图)的话,可以通过$T$极大的数据。
然后我们发现这个题的攻击次数是$10^{18}$。
(一回合打$50$火妖法还能够到(还把我超生德直接打死),但这尼玛$10^{18}$难不成要无限回合火球法?)
再枚举$N$进行转移显然不可取,所以进行矩阵优化。
容易发现总状态数为固定的$165$个。(模型:$0,1,2,3$每种$k$个和为$7$,$k$可以为$0$)
那么我们将所有状态排成一列,形成一个$1\times 165$的初始矩阵。
状态间互相转移的路径是相同的,每次转移的概率也是相同的,
那么可以构造一个$165\times 165$的转移矩阵,答案相当于$\{初始矩阵\times (转移矩阵^N)\}$。
使用矩阵快速幂即可。(需要卡常)
但$165\times 165$不好维护答案,再加$1$维变为$166\times 166$即可维护打脸期望。
代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 100005 #define MAXM 10 #define MAXK 170 #define MAXL 63 #define INF 0x7fffffff #define ll long long const ll MOD=998244353; const ll mod=(0x7fffffffffffffffll/MOD-MOD)*MOD; ll inv[MAXK],sta[MAXM][MAXM][MAXM],res[MAXK],tmp[MAXK],num; struct Matrix{ ll A[MAXK][MAXK]; Matrix() {memset(A,0,sizeof(A));} }m[MAXL+5]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline ll power(ll a,ll b){ ll ans=1; while(b){ if(b&1) ans*=a%MOD,ans%=MOD; b>>=1,a*=a,a%=MOD; } //cout<<ans<<endl; return ans%MOD; } inline void mul(Matrix a){ memset(tmp,0,sizeof(tmp)); for(ll i=1;i<=num+1;i++){ for(ll j=1;j<=num+1;j++){ //if(a[j]*b.A[j][i]<0) cout<<a[j]<<" "<<b.A[j][i]<<endl; tmp[i]+=res[j]*a.A[j][i]; if(tmp[i]>=mod) tmp[i]-=mod; } tmp[i]%=MOD; } memcpy(res,tmp,sizeof(tmp)); return; } inline Matrix sq(Matrix a){ Matrix ans; for(ll i=1;i<=num+1;i++) for(ll j=1;j<=num+1;j++){ for(ll k=1;k<=num+1;k++){ ans.A[i][j]+=a.A[i][k]*a.A[k][j]; if(ans.A[i][j]>=mod) ans.A[i][j]-=mod; } ans.A[i][j]%=MOD; } return ans; } int main(){ //freopen("1.txt","w",stdout); ll T=read(),M=read(),K=read();num=0; for(ll i=1;i<=K+1;i++) inv[i]=power(i,MOD-2); if(M==1) for(ll i=0;i<=K;i++) sta[i][0][0]=++num; if(M==2) for(ll i=0;i<=K;i++) for(ll j=0;j<=K-i;j++) sta[i][j][0]=++num; if(M==3) for(ll i=0;i<=K;i++) for(ll j=0;j<=K-i;j++) for(ll k=0;k<=K-i-j;k++) sta[i][j][k]=++num; if(M==1){ for(ll i=0;i<=K;i++){ ll P=inv[i+1]; if(i) m[0].A[sta[i][0][0]][sta[i-1][0][0]]=P*i%MOD; m[0].A[sta[i][0][0]][sta[i][0][0]]=m[0].A[sta[i][0][0]][num+1]=P; } } if(M==2){ for(ll i=0;i<=K;i++) for(ll j=0;j<=K-i;j++){ ll P=inv[i+j+1]; if(i) m[0].A[sta[i][j][0]][sta[i-1][j][0]]=P*i%MOD; if(j) m[0].A[sta[i][j][0]][sta[i+1][(i+j<K)?j:j-1][0]]=P*j%MOD; m[0].A[sta[i][j][0]][sta[i][j][0]]=m[0].A[sta[i][j][0]][num+1]=P; } } if(M==3){ for(ll i=0;i<=K;i++) for(ll j=0;j<=K-i;j++) for(ll k=0;k<=K-i-j;k++){ ll P=inv[i+j+k+1]; if(i) m[0].A[sta[i][j][k]][sta[i-1][j][k]]=P*i%MOD; if(j) m[0].A[sta[i][j][k]][sta[i+1][j-1][(i+j+k<K)?k+1:k]]=P*j%MOD; if(k) m[0].A[sta[i][j][k]][sta[i][j+1][(i+j+k<K)?k:k-1]]=P*k%MOD; m[0].A[sta[i][j][k]][sta[i][j][k]]=m[0].A[sta[i][j][k]][num+1]=P; } } m[0].A[num+1][num+1]=1; for(ll i=1;i<=MAXL;i++) m[i]=sq(m[i-1]); while(T--){ ll N=read(),cnt=0; memset(res,0,sizeof(res)); if(M==1) res[sta[1][0][0]]=1; if(M==2) res[sta[0][1][0]]=1; if(M==3) res[sta[0][0][1]]=1; while(N){ if(N&1) mul(m[cnt]); N>>=1,cnt++; } printf("%lld\n",res[num+1]); } return 0; }