貌似就因为这名字我也要做啊。。。
总结一下今天题的给我们队的感觉。。复杂度。。。不要不敢想。。
A
problem
给你n个人和T个询问,每个人有两个值pi和qi,pi表示你给他一瓶nomal可乐他给你的cup数量,qi表示你给他一瓶gift可乐他给你的cup数量。你有无限多nomal可乐和0个gift可乐。你给每个人一瓶可乐。你可以借瓶盖但是最终要还上。
T组询问每组询问一个数m,表示m个cup可以换一瓶gift可乐。问这时你最多可以有多少cup。
think
对于第i个人,你可以给他一瓶nomal可乐,得到pi个cup,也可以给他一个gift可乐得到qi-m个cup(这就是所谓的可以借瓶盖吧)
所以答案就是sum( max(pi, qi-m) )
我们按照pi-qi排序。(当然按照qi-pi也可以的)
对于m,二分找到最后一个小于等于m的位置。前面的是sum(qi-m) 后面的是sum(pi)
code
int fk[N]; //这个用于二分的数组是小队友写的。。我从不如此命名。。
struct point{
int p, q, x, sump, sumq;
}pp[N];
bool cmp(point a, point b){
return a.x < b.x;
}
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF){
for(int i = 0; i < n; ++i){
scanf("%d%d", &pp[i].p, &pp[i].q);
pp[i].x = pp[i].p - pp[i].q;
}
sort(pp, pp + n, cmp);
pp[0].sump = pp[0].p;
pp[0].sumq = pp[0].q;
fk[0] = pp[0].x;
for(int i = 1; i < n; ++i){
pp[i].sump = pp[i-1].sump + pp[i].p;
pp[i].sumq = pp[i-1].sumq + pp[i].q;
fk[i] = pp[i].x;
}
fk[n] = INF;
int a;
while(m--){
scanf("%d", &a);
int l = 0, r = n - 1, mid = 0;
mid = upper_bound(fk, fk + n, -a) - fk;
if (fk[mid] > -a) mid--;
int ans = pp[mid].sumq - a*(mid+1) + pp[n-1].sump - pp[mid].sump;
printf("%d\n", ans);
}
}
return 0;
}
F
problem
给你n和m,n表示有n个数,m表示n里面m个1(其余是0),问这样能不能赢。和n个里面至少有几个1可以赢。
赢是这样定义的:对于n,如果里面超过一半的1,那么他就是赢。或者把n等分成a组,这a组里面有超过一半的组赢,那么n赢。n不能赢就是输了。
think
我们想的是,分组的时候,分成2的某次方和所有质数。比如n是120就分解成,8, 3, 5。然后每个数都是(a/2+1)然后乘起来。比如120就是 5*2*3 = 30.
但是这样不对,不知道为什么。。。AC了的是爆搜,非常暴力,但是秒过了。看代码吧。
code
map<int, int> mp;
int gao(int m) {
if(mp[m] != 0) return mp[m];
int mm = sqrt(m + 0.5);
int ans = m/2 + 1;
for(int i = 2; i <= mm; ++i){
if(m % i == 0){
ans = min(ans, gao(i) * gao(m/i)); //这两种都AC
// ans = min(ans, (i / 2 + 1) * gao(m / i));
// ans = min(ans, (m / i / 2 + 1) * gao(i));
}
}
return mp[m] = ans;
}
int main ()
{
int n, m;
while(scanf("%d%d", &m, &n) != EOF) {
int dd = gao(m);
if (dd > n) puts("No");
else puts("Yes");
printf("%d\n", dd);
}
return 0;
}
G
problem
给你a和b表示两个队的人数。给你n和n个数(这n个数和是a+b)表示有n堆人和每堆人的数量。
每堆人要么都是某一个队要么一队一半。
think
很简单是DP,但是我以为复杂度会太大,但是秒过了。一般滚动数组会减慢时间。所以我以为会T所以滚动了。50 case啊。
dp[n][a] 表示到第n堆人已经有了a个第一个队的人。
code
const int mod = 1000000007;
const int N = 222;
const int M = 100010;
int zhu[2][M];
int main(){
int n, m, a;
while(scanf("%d%d", &n, &m) != EOF){
memset(zhu, 0, sizeof(zhu));
int nn;
scanf("%d", &nn);
zhu[0][0] = 1;
for(int i = 1; i <= nn; ++i){
int ii = i & 1;
for(int j = 0; j <= n; ++j) zhu[ii][j] = 0;
scanf("%d", &a);
for(int j = 0; j <= n; ++j){
zhu[ii][j] += zhu[1-ii][j];
if(zhu[ii][j] >= mod) zhu[ii][j] -= mod;
if(a + j <= n) {
zhu[ii][a+j] += zhu[1-ii][j];
if(zhu[ii][a+j] >= mod) zhu[ii][a+j] -= mod;
}
if(a%2 == 0 && a/2 + j <= n) {
zhu[ii][a/2+j] += zhu[1-ii][j];
if(zhu[ii][a/2+j] >= mod) zhu[ii][a/2+j] -= mod;
}
}
}
printf("%d\n", zhu[nn&1][n]);
}
return 0;
}
I
problem
一棵树。每条边有两个值,一个是距离,一个数破坏这表边的代价。
root是所有点里面,距离最远的边最近的那个点。
要想让所有叶子和root之间不连通。求最大的最小破坏的值。
think
找root是用有up dw1 dw2那种树直径的树DP, 模版。
然后dfs一遍就找到答案了。
code
const int N = 11111;
struct point{
int u;
LL a, b;
point(){};
point(int _u, LL _a, LL _b){
u = _u; a = _a; b = _b;
}
};
vector<point > edge[N];
LL up[N];
LL dw1[N];
LL dw2[N];
int root;
LL tmp;
LL ans;
void dfs1(int s, int pre){
dw1[s] = dw2[s] = 0;
int i, ss;
LL dd;
for(int i = 0, len = edge[s].size(); i < len; ++i){
ss = edge[s][i].u;
if(ss == pre) continue;
dfs1(ss, s);
dd = edge[s][i].a + dw1[ss];
if(dd >= dw1[s]){
dw2[s] = dw1[s];
dw1[s] = dd;
}
else if(dd > dw2[s]){
dw2[s] = dd;
}
}
}
void dfs2(int s, int pre, LL D){
int i, ss;
LL dd;
if(dw1[pre] == dw1[s] + D) up[s] = D + max(up[pre], dw2[pre]);
else up[s] = D + max(up[pre], dw1[pre]);
LL tt = max(dw1[s], up[s]);
if(tt < tmp){
root = s;
tmp = tt;
}
for(int i = 0, len = edge[s].size(); i < len; ++i){
ss = edge[s][i].u;
if(ss == pre) continue;
dd = edge[s][i].a;
dfs2(ss, s, dd);
}
}
void dfs3(int s, int pre, LL D){
int len = edge[s].size();
if(len == 1){
ans = max(ans, D);
return;
}
for(int i = 0; i < len; ++i){
int ss = edge[s][i].u;
if(ss == pre) continue;
dfs3(ss, s, min(D, edge[s][i].b));
}
}
int main(){
int n;
while(scanf("%d", &n) != EOF){
if(n == 0){
puts("0");
continue;
}
int u, v;
LL a, b;
for(int i = 0; i <= n; ++i) edge[i].clear();
for(int i = 1; i < n; ++i){
scanf("%d%d%lld%lld", &u, &v, &a, &b);
edge[v].push_back(point(u, a, b));
edge[u].push_back(point(v, a, b));
}
ans = 0;
tmp = 1000000000;
root = 1;
dfs1(1, 0);
dfs2(1, 0, 0);
dfs3(root, 0, 1000000000LL);
printf("%lld\n", ans);
}
return 0;
}
J
problem
给你一个数n。要让c0*n^3 + c1*(n-1)^3 …… c(n-1)*1^3的绝对值最小。
其中c0, c1, ……c(n-1)是‘+’ 或‘-’
think
传说超过15那个式子就是0或1.所以不会有负数出现。
然后又是我们以为复杂度会非常高的爆搜。不会想想中间确实会很小。
code
//别人的code
typedef long long ll;
const char ans[20][20]={
"",
"+",
"-+",
"-++",
"-+++",
"-++++",
"-+++-+",
"+---+++",
"+--+--++",
"+--+-+--+",
"-+-+++++++",
"-++-+--++++",
"+--++---+-++",
"--+++++-+++++",
"-++-+--++--+-+"
};
char s[10010];
ll s3[10010];//预处理三次方
bool dfs(ll sum,int n){
for(int i=n;i>0;i--)if(sum>=s3[i]){
if(sum - s3[i]==0 || dfs(sum - s3[i],i-1)){
s[i-1]='+';
return true;
}
}
return false;
}
void solve(ll n){
for(int i=0;i<n;i++)s[i]='-';
ll sum=n*n*(n+1)*(n+1)/8;
dfs(sum,n);
for(int i=n-1;i>=0;i--)putchar(s[i]);
puts("");
}
int main()
{
for(ll i=1;i<=10000;i++) s3[i]=i*i*i;
int n;
while(~scanf("%d",&n)){
if(n<15)puts(ans[n]);
else solve(n);
}
return 0;
}