# 埃森哲杯上海大学春季赛暨金马五校赛题解汇总

------------------------------------------------------------------------------------------------------------------

A：题意是只能对第一堆的土进行移动，求花费代价最小。

#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

const int maxn = 1e5+10;
int a[maxn],b[maxn],c[maxn];
int n;
typedef long long LL;

inline int Min(int x, int y) {return x<y?x:y; }
inline int Max(int x, int y) {return x>y?x:y; }

int main(){
int T;
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=1; i<=n; i++) scanf("%d",&a[i]);
for (int i=1; i<=n; i++) scanf("%d",&b[i]);
for (int i=1; i<=n; i++) c[i] = a[i] - b[i];

LL ans = 0;
int idx = 1;
for (int i=1; i<=n; i++) if (c[i]<0) {
while (c[i]<0) {
while (idx<=n && c[idx]<=0) idx++;
int t = Min(-c[i],c[idx]);
ans += (LL)t*abs(i-idx);
c[i] += t; c[idx] -= t;
}
}
printf("%lld\n",ans);
}
return 0;
}

B：对于当前节点i，i*F(i) 相当于累加F(i)个i，F(i)是第i个节点有的合约数节点个数。

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

typedef long long LL;
const int mod = 1e9+7;
const int maxn = 2e4+10;
vector<int>g[maxn];
vector<int>vec[maxn];
bool vis[maxn];
int w[maxn];
int n,p;

LL cnt[maxn];
LL ans;

void init(){
for (int i=1; i<=10000; i++) vis[i] = 1;
for (int i=2; i<=10000; i++) if (vis[i]) {
for (int j=i+i; j<=10000; j+=i) vis[j] = 0;
}
for (int i=2; i<=10000; i++) if (vis[i]==0) {
for (int j=i; j<=10000; j+=i) vec[j].push_back(i);
}
}

void dfs(int u, int fa) {
int v;
for (int i=0; i<vec[w[u]].size(); i++) {
v = vec[w[u]][i];
cnt[v] = (cnt[v] + u + mod)%mod;
}
ans = (ans+cnt[w[u]])%mod;
for (int i=0; i<g[u].size(); i++) {
if (g[u][i] == fa) continue;
dfs(g[u][i],u);
}

for (int i=0; i<vec[w[u]].size(); i++) {
v = vec[w[u]][i];
cnt[v] = (cnt[v] - u + mod)%mod;
}
}

int main(){
//freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
init();

while (T--) {
ans = 0;
scanf("%d%d",&n,&p);
for (int i=1; i<=n; i++) g[i].clear();
for (int i=1; i<=n; i++) cnt[i] = 0;
int v,u;
for (int i=1; i<n; i++) {
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
for (int i=1; i<=n; i++) scanf("%d",&w[i]);
dfs(p,-1);
printf("%lld\n",ans);
}
return 0;
}

C：n<=9,利用STL的next_permutation枚举全排列，然后统计转换次数

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 20;
const int INF = 1e9;
int a[maxn],b[maxn],p[maxn];
int cost[maxn][maxn];
int n;

int calc(int x, int y) {
if (x == y) return 0;
if (x < y) swap(x,y);

int res = 0;
while (x>y) {
if (x%2 == 0) {
if (x/2>=y) { x>>=1; res++; }
else {res += x-y; return res; }
}
else {
--x;
res++;
}
}
return res;
}

int t[maxn];
int solve(){
memcpy(t,p,sizeof(t));
int res = 0;
for (int i=1; i<=n; i++) {
if (t[i] != i) {
for (int j=i+1; j<=n; j++) if (t[j] == i) {
swap(t[j],t[i]);
break;
}
res++;
}
}
return res;
}

int main(){
int T;
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=1; i<=n; i++) scanf("%d",&a[i]);
for (int j=1; j<=n; j++) scanf("%d",&b[j]);
for (int i=1; i<=n; i++) p[i] = i;

for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++) cost[i][j] = calc(a[i],b[j]);

int ans = INF;
do {
int f = 0;
for (int i=1; i<=n; i++) f += cost[p[i]][i];
f += solve();
if (f<ans) ans = f;
}while (next_permutation(p+1,p+n+1));
printf("%d\n",ans);
}
return 0;
}

D: 这题个人认为很难，无法证明结论，但写起来就几行代码。。

#include <cstdio>
using namespace std;

int L1,R1,L2,R2,mod;
int main(){
int T;
scanf("%d",&T);
while (T--) {
scanf("%d%d%d%d%d",&L1,&R1,&L2,&R2,&mod);
if (R2-L2+1>=mod) printf("LOSE\n");
else printf("WIN\n");
}
return 0;
}

E：签到题~

#include <cstdio>
#include <map>
using namespace std;

typedef long long LL;
LL ans,n;

int main(){
while (scanf("%lld",&n)==1){
ans = (LL)1<<n;
printf("%lld\n",ans);
}
return 0;
}

F：找规律题，可以发现满足要求的x转换为二进制后，不会有相邻两位都为1

#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 70;
typedef long long LL;
LL f[maxn],sum[maxn];
LL n;

void init(){
f[0] = f[1] = 1;
for (int i=2; i<=60; i++) f[i] = f[i-1] + f[i-2];
sum[0] = 1;
for (int i=1; i<=60; i++) sum[i] = sum[i-1] + f[i];
}

int main(){
init();
//freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
while (T--){
scanf("%lld",&n);
LL ans = 0;
bool isfirst = 1;
while (n>0) {
if (isfirst) {
if (n == 1) {
ans = 1LL;
break;
}
int i=0;
while (sum[i]<n) i++;
ans += 1LL<<i;
n -= sum[i-1];
isfirst = 0;
}
else {
n = n-1;
int i = 0;
if ( n == 1) {
ans = ans+1;
break;
} else if ( n == 0) {
break;
}
while (sum[i]<n) i++;
ans += 1LL<<i;
n -= sum[i-1];
}
}
printf("%lld\n",ans);
}
return 0;
}

G：待填坑

H：待填坑

I：签到题，不过不知道为什么官方题解那么说那么多。。。

1：第i位数字为9，把第i位数字减一，i+1到n位数字全部变为8

2：看第i位之后的数字是否都是4 或出现 若干个4然后接着一个比4小的数字，是的话第i位数字减一，后面i+1到n位全部为8

3：以上两种情况不满足，把第i位数字加一，后面全部变为0

#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 100000+10;
char s[maxn];
int n;

int main(){
int T;
scanf("%d",&T);
while (T--){
scanf("%s",s);
n = strlen(s);
int st = 0;
while (st<n && (s[st]-'0')%2==0 ) st++;

if (st>=n) {
printf("%s\n",s);
continue;
}

if (n == 1 && s[0] == '1') {
printf("0\n");
continue;
}

int nxt = st+1;
while (nxt<n && s[nxt]=='4') nxt++;
if (nxt>=n || s[nxt]<'4' || s[st]=='9') {
s[st]--;
for (int i=st+1; i<n; i++) s[i] = '8';
}
else {
s[st]++;
for (int i=st+1; i<n; i++) s[i] = '0';
}

int f = 0; while (f<n && s[f]=='0') f++;
for (int i=f; i<n; i++) printf("%c",s[i]); printf("\n");
}
return 0;
}

J：二分+二分图最大匹配

#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

const int INF = 1e9+7;
const int maxn = 200+10;
int n,m;
int a[maxn],b[maxn];
int g[maxn][maxn];
int left[maxn];
bool vis[maxn];

bool match(int u) {
for (int i=1; i<=m; i++) if (g[i][u] && !vis[i]) {
vis[i] = 1;
if (!left[i] || match(left[i])) {
left[i] = u;
return 1;
}
}
return 0;
}

bool judge(int mid) {
for (int i=1; i<=m; i++) {
for (int j=1; j<=n+1; j++) {
g[i][j] = 1;
if (j>1 && abs(b[i]-a[j-1])>mid) g[i][j] = 0;
if (j<n+1 && abs(b[i]-a[j])>mid) g[i][j] = 0;
}
}

int ans = 0;
for (int i=1; i<=n+1; i++) left[i] = 0;
for (int i=2; i<=n; i++) {
if (abs(a[i-1]-a[i])>mid) {
for (int j=1; j<=m; j++) vis[j] = 0;
if (!match(i)) return 0;
ans++;
}
}

for (int i=1; i<=n+1; i++) {
if (i==1 || i==n+1 || abs(a[i-1]-a[i])<=mid) {
for (int j=1; j<=m; j++) vis[j] = 0;
ans += match(i);
}
}
return ans == m;
}

int main(){
int T;
scanf("%d",&T);
while (T--){
scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) scanf("%d",&a[i]);
for (int j=1; j<=m; j++) scanf("%d",&b[j]);

int left = 0, right = INF;
int mid, ans = 0;
while (left <= right) {
mid = (left+right)>>1;
if (judge(mid)) {
right = mid-1;
ans = mid;
}
else left = mid+1;
}
printf("%d\n",ans);
}
return 0;
}

K：待填坑

L：这题卡了N久，最后还没写出来，太菜了。。。据说出题人搞错了，数据当成连续子串也A了。。。

emmm，正解DP我还没想出来，平时练习DP太少了。。

#include <cstdio>
using namespace std;

typedef long long LL;
const int maxn = 1e5+10;
int a[maxn];
LL sum[maxn];
int n,k;

inline int Max(int x, int y) {return x>y?x:y; }
int main(){
while (scanf("%d%d",&n,&k) == 2) {
for (int i=1; i<=n; i++) {
scanf("%d",&a[i]);
a[i] %= k;
}
sum[0] = 0;
for (int i=1; i<=n; i++) sum[i] = sum[i-1] + a[i];

int mx = -1;
for (int i=1; i<=n; i++) {
for (int j=n; j>=i; j--) if ((sum[j]-sum[i-1])%k == 0) {
mx = Max(mx,j-i+1);
break;
}
}
printf("%d\n",mx);
}
return 0;
}