2018.8.16提高B组模拟考试
永远不要把时间交给一道打表题。
T1 题意简述:jzoj4674
解题思路:emmm打表。
首先登陆OEIS查询算出数列的前几项,发现数列每15项有如下规律:
1 ~15:A
16~30:AB
31~45:ABB
因此可以把n先分解为a*15+b,然后分别计算。
A与B的前缀和都可以打表算出,系数可以用等比数列求和公式推出。需要逆元。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define ll long long #define MOD 123454321 #define ny 96007682 using namespace std; ll n,ans,a,b; ll sum1[16]={0,1,3,6,10,42,165,208,2331,2763,3997,36120,79332,113653,137085,260517}; ll sum2[16]={0,234321,577533,1009656,1330890,1454322,1886445,2098788,2530911,2654343,2975577,3407700,3750912,3985233,4108665,4232097}; //ll a[51]={0, //1 ,2 ,3 ,4 ,32 ,123 ,43 ,2123 ,432 ,1234 ,32123 ,43212 ,34321 ,23432 ,123432 , //1234321,2343212,3432123,4321234,32123432,123432123,43212343,2123432123,432123432,1234321234,32123432123,43212343212,34321234321,23432123432,123432123432}; ll qpow(ll x,ll y) { ll ans=1; while(y) { if(y&1) ans=ans*x%MOD; x=x*x%MOD; y/=2; } return ans; } ll gcd(ll x,ll y) { return (y? gcd(y,x%y):x); } int main() { scanf("%lld",&n); a=n/15,b=n%15; ll a1=qpow(1000000,a); a1--;a1=a1*ny%MOD; ll a2=a1-a; while(a2<0) a2+=MOD; a2=a2*ny%MOD; ll a4=a1; ll a3=qpow(1000000,a+1); a3--;a3=a3*ny%MOD; a3=(a3-a1+MOD)%MOD; ans=(a1*sum1[15]%MOD+a2*sum2[15]%MOD+a3*sum1[b]%MOD+a4*sum2[b]%MOD)%MOD; printf("%lld\n",ans%MOD); return 0; }
T2 题意简述:jzoj1321
解题思路:布鲁特佛斯算法。(滑稽)
(注:布鲁特佛斯 = Brute Force = 暴力)
注意到n只有35,非常小。可以考虑枚举。
但是2^35是肯定要超时的。考虑折半搜索(meet in the middle)。
只需把前一半的所有情况枚举出来,存在vector内,再枚举后一半,二分查找vector内是否
有与当前情况异或起来等于(1<<n)-1的元素。有则更新答案。
为了方便调试,我加了一个用于去重的map。不加应该也可以。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<vector> #include<map> #define INF 0x3f3f3f3f #define ll long long using namespace std; ll n,m,ans=INF,mid,cnt,tot,head[40],vis[40],pos[40]; struct uio{ ll nxt,to; }edge[1200]; struct oiu{ ll num,pos; }; vector<oiu> vec; map<ll,ll> mp; struct cmp{ bool operator () (const oiu &a,const oiu &b) const{ return a.pos<b.pos; } }; void add(ll x,ll y) { edge[++cnt].nxt=head[x]; edge[cnt].to=y; head[x]=cnt; } void dfs(ll x,ll num,ll p) { if(!mp[p]) vec.push_back({num,p}),mp[p]=vec.size(); else vec[mp[num]].num=min(vec[mp[num]].num,num); if(x==mid+1ll) return; dfs(x+1ll,num,p); dfs(x+1ll,num+1ll,p^pos[x]); } void dfs1(ll x,ll num,ll p) { oiu tmp={num,p^((1ll<<n)-1ll)}; vector<oiu>::iterator it=lower_bound(vec.begin(),vec.end(),tmp,cmp()); if(it!=vec.end()) if(((*it).pos^p)==((1ll<<n)-1ll)) ans=min(ans,(*it).num+num); if(x==n+1ll) return; dfs1(x+1ll,num,p); dfs1(x+1ll,num+1ll,p^pos[x]); } int main() { scanf("%lld%lld",&n,&m); mid=(1ll+n)>>1ll; for(ll i=1ll;i<=m;i++) { ll u,v; scanf("%lld%lld",&u,&v); add(u,v),add(v,u); } for(ll i=1ll;i<=n;i++) { for(ll j=head[i];j;j=edge[j].nxt) vis[edge[j].to]=1ll; vis[i]=1ll;ll k=0; for(ll j=1ll;j<(1ll<<n);j<<=1ll){k++;if(vis[k]) pos[i]+=j,vis[k]=0;} } dfs(1ll,0,0); sort(vec.begin(),vec.end(),cmp()); // for(ll i=0;i<vec.size();i++) // printf("%lld %lld\n",vec[i].pos,vec[i].num); dfs1(mid+1ll,0,0); printf("%lld\n",ans); return 0; }
T3 题意简述:jzoj1322
解题思路:dp。
设dp[i][j]为剩余i个硬币,上一轮对手取了j个硬币的最大收益。
dp[i][j]=max(sum(i-k+1,i)+sum(1,i-k)-dp[i-k][k])。
sum(i-k+1,i)表示本轮自己取了从i-k+1到i共k枚硬币。
sum(1,i-k)-dp[i-k][k]表示下一轮对手取完后自己最多还能取多少硬币。
发现转移是O(n^3)的,考虑化简。
发现dp[i][j]可以由dp[i][j-1]转移而来,方程如代码中所示。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; int n,sum[2001],dp[2001][2001]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&sum[n-i+1]); for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; for(int i=1;i<=n;i++) for(int j=1;j<=n-i+1;j++) { dp[i][j]=dp[i][j-1]; int k=2*j-1; if(i>=k) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]); if(i>=++k) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]); } // for(int k=1;k<=2*j&&k<=i;k++) // dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]); printf("%d\n",dp[n][1]); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步