按照题意模拟就好
也许可能要当心那个除法?建议转换成乘法做
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int T,dry,wet,harm,t,n,i,x;
char ch[3000],s[100];
int main()
{
scanf("%d",&T);
fo(t,1,T)
{
dry = wet = harm = 0;
scanf("%s",ch+1); n = strlen(ch+1);
scanf("%s",s+1);
fo(i,1,n)
{
x = ch[i] - 'a' + 1;
if (s[x] == 'd') dry++;
if (s[x] == 'w') wet++;
if (s[x] == 'h') harm++;
}
cout<<"Case #"<<t<<": ";
if (4 * harm >= n) cout<<"Harmful"<<endl;
else if (10 * harm <= n) cout<<"Recyclable"<<endl;
else if (dry >= 2 * wet) cout<<"Dry"<<endl;
else cout<<"Wet"<<endl;
}
return 0;
}
同样也是按照题意模拟就好
首先把不省略 0 0 0的答案做出来,然后考虑去掉哪一段 0 0 0
这个问题需要大量的讨论…
比如 ′ 0 ′ '0' ′0′和 ′ : ′ ':' ′:′的 a s c i i ascii ascii码,比如两端的 ′ 0 ′ '0' ′0′和中间的 ′ 0 ′ '0' ′0′去掉后对于长度的贡献不一样
当然最方便的做法就是暴力去掉每一段 ′ 0 ′ '0' ′0′然后排个序就好了
首先看到这个题的过题比率就能猜到肯定不是简单的二分。。。
题解是真的厉害,我都构造不出什么hack
155
39 39 39 39 39 60 60 60 60 60 100 100 100 100 100
199 为一个合法的答案,但 200 不是,201 也不是。
最后我们的过题策略是。。:
首先我猜测这个题大范围上一定满足二分的性质
那么哪里有可能不满足?我猜是边界条件
所以我先从下界开始check300个答案(复杂度上限)如果找不到答案就再二分
然后就a了。。。
然后开始瞎搞。。发现只要额外check下界就好了。。
现在复盘一下,感觉就是不满足二分的答案少之又少,而这些答案都有一个特点:他们都是下界
实际上这个题,我的下界是对的( ⌈ s u m k ⌉ \lceil \frac{sum}{k} \rceil ⌈ksum⌉)但是我的上界算大了
对于一个答案 a n s ans ans,如果它非法,那么显然每个箱子空余空间 < m a x V <maxV <maxV
所以有 k ∗ ( a n s − m a x V + 1 ) ≤ s u m k*(ans-maxV+1) \le sum k∗(ans−maxV+1)≤sum(即并没有装下所有物品)
化简一下就可以得到 a n s ≤ s u m / k + m a x V − 1 ans \le sum/k+maxV-1 ans≤sum/k+maxV−1
所以上界就是 ⌈ s u m k ⌉ + m a x V − 1 \lceil \frac{sum}{k} \rceil+maxV-1 ⌈ksum⌉+maxV−1
这个区间长度只有 m a x V maxV maxV,所以暴力check每一个答案就好
时间复杂度是 O ( m a x V n l o g n ) O(maxVnlogn) O(maxVnlogn),跑满的话是 2 e 8 2e8 2e8(实际上怎么可能跑满)
当然你也可以强行二分
f ( x ) , f ( x + m a x V ) , f ( x + 2 ∗ m a x V ) , . . . , f ( x + k ∗ m a x V ) f(x),f(x+maxV),f(x+2*maxV),...,f(x+k*maxV) f(x),f(x+maxV),f(x+2∗maxV),...,f(x+k∗maxV)是单调的
(每个箱子都大 m a x V maxV maxV那肯定能多装嘛(虽然感觉还是有点怀疑))
所以可以先二分再暴力check,时间复杂度会多一个log,不过考虑到check成功的概率极高,所以还是能过的(吧)
#include
using namespace std;
multiset <int> ms;
int n,k,a[1005];
bool check(int x)
{
ms.clear();
for (int i=0;i<n;i++)
ms.insert(a[i]);
for (int i=0;i<k;i++)
{
int tmp=x;
while (true)
{
if (!ms.size()) break;
if (tmp<*(ms.begin())) break;
else
{
multiset<int> ::iterator it=ms.upper_bound(tmp);
it--;
int cur=*it;
ms.erase(ms.lower_bound(cur));
tmp-=cur;
}
}
}
if (ms.size()) return false;
return true;
}
int main()
{
int t;
scanf("%d",&t);
for (int cas=1;cas<=t;cas++)
{
scanf("%d%d",&n,&k);
for (int i=0;i<n;i++)
scanf("%d",&a[i]);
int flag = 0;
int l = 0; for (int i=0;i<n;i++) l += a[i];
if (l % k == 0) l = l / k; else l = l / k + 1;
for (int i=l;i<=l;i++)
if (check(i))
{
int ans = i;
printf("Case #%d: %d\n",cas,ans);
flag = 1;
break;
}
if (flag) continue;
int ans=1e6;
while (true)
{
int l=1,r=ans;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(mid)) r=mid-1;
else l=mid+1;
}
if (l==ans) break;
else ans=l;
}
printf("Case #%d: %d\n",cas,ans);
}
return 0;
}
听说还有论文呢orz
场上没做出来真的好可惜
首先根据边是偶数这个条件可以得到 n = 4 k 或 者 4 k + 1 n=4k或者4k+1 n=4k或者4k+1
然后就可以分成 4 4 4堆,每堆 k k k个
其中两堆是团(两两连边)另外两堆是独立集(互不连边)
之后对这 4 4 4堆按照 n = 4 n=4 n=4的情况进行堆与堆之间的连边
如果多一个点,就把这个点和两个团连
注意, n = 4 n=4 n=4的时候是一条链,那么我们要保证中间两堆应该都是团(或者都是独立集)
至于为什么。。手动模拟就知道错哪了
换个通俗易懂的讲法:
[1,k]连[k+1,2k] [k+1,2k]连[2k+1,3k] [2k+1,3k]连[3k+1,4k]
然后[k+1,2k]和[2k+1,3k]自己内部连
如果多一个怎么办?那就向[k+1,3k]连
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int T,cas,x,i,j,n;
int f[2005][2005],g[2005];
int main()
{
cin>>T;
fo(cas,1,T)
{
cin>>x;
cout<<"Case #"<<cas<<": ";
if (x % 4 != 0 && x % 4 != 1) {cout<<"No"<<endl; continue;} else cout<<"Yes"<<endl;
n = x / 4;
fo(i,1,x) fo(j,1,x) f[i][j] = 0;
fo(i,1,n) fo(j,1,n) if (i != j) f[i][j] = 1;
fo(i,n+1,2*n) fo(j,n+1,2*n) if (i != j) f[i][j] = 1;
fo(i,1,n) fo(j,n+1,2*n) if (i != j) f[i][j] = f[j][i] = 1;
fo(i,1,n) fo(j,2*n+1,3*n) if (i != j) f[i][j] = f[j][i] = 1;
fo(i,n+1,2*n) fo(j,3*n+1,4*n) if (i != j) f[i][j] = f[j][i] = 1;
if (x % 4 == 1) fo(i,1,2*n) f[x][i] = f[i][x] = 1;
fo(i,1,n) g[i] = 2*n+i;
fo(i,n+1,2*n) g[i] = 2*n+i;
fo(i,2*n+1,3*n) g[i] = i-n;
fo(i,3*n+1,4*n) g[i] = i-3*n;
if (x % 4 == 1) g[x] = x;
fo(i,1,x)
{
fo(j,1,x) cout<<f[i][j]; cout<<endl;
}
fo(i,1,x-1) cout<<g[i]<<" "; cout<<g[x]<<endl;
}
return 0;
}
是个乱搞题,还是非常有意思的(主要是因为自己做出来了23333)
首先介绍我的做法
做这个题的时候第一反应就是这个解不会很多(怎么可能这么凑巧两组对应方案下一堆日期都是星期五?)
所以我首先全排列,然后随机取出100个日期check是不是星期五
在绝大部分情况下,这100次check足够了
如果有数据能够完美的躲过100次check,那说明这组数据大部分应该是满足的
然后我对这组数据全体check
这个时候最关键的部分来了:我认为如果这组数据有75%以上都是星期五,但不全是星期五,那就是 i m p o s s i b l e impossible impossible
因为我觉得怎么可能存在两组对应使得大部分日期都是星期五嘛
调了一下参突然就a了哈哈哈哈哈
其实这题不管怎么调参都是可以的,一开始wa的原因是因为没有暴力判断小数据
毕竟一组数据如果有一半不是星期五,那它躲过100次测试的概率低达 1 2 100 \frac{1}{2^{100}} 21001
当时做题的时候简直就是 M i l l e r Miller Miller R o b i n Robin Robin附体哈哈哈哈借鉴了那个素性测试的思想
std的做法大同小异
实际上直接按照全排列一个一个check过来就行了,基本上check几个就会break
哦还有,数据中有一组应该是一堆重复日期,所以如果不去重的话就会T
然而我加了0.75就神奇的逃过一劫23333
改成0.74就T了哈哈哈哈
看来是猜中了出题人的数据
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int DateToInt(int m,int d,int y)
{
return 1461*(y+4800+(m-14)/12)/4+367*(m-2-(m-14)/12*12)/12-3*((y+4900+(m-14)/12)/100)/4+d-32075;
}
bool isfri(int m,int d,int y)
{
return DateToInt(m,d,y)%7==4;
}
int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool isdate(int m,int d,int y)
{
if (y < 1600 || y > 9999) return false;
if (m < 1 || m > 12) return false;
int feb;
if (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)) day[2] = 29; else day[2] = 28;
if (d < 1 || d > day[m]) return false;
return true;
}
int T,cas,n,i,times,k,flag,t,yy,mm,dd,num;
char ch[100];
int f[100005][10];
int b[100005];
int a[15];
int main()
{
srand(233336666);
scanf("%d",&T);
fo(cas,1,T)
{
scanf("%d",&n);
fo(i,1,n)
{
scanf("%s",ch);
f[i][1] = ch[0] - 'A';
f[i][2] = ch[1] - 'A';
f[i][3] = ch[2] - 'A';
f[i][4] = ch[3] - 'A';
f[i][5] = ch[5] - 'A';
f[i][6] = ch[6] - 'A';
f[i][7] = ch[8] - 'A';
f[i][8] = ch[9] - 'A';
}
fo(i,0,9) a[i]=i;
times = 0;
do
{
//fo(i,0,9) printf("%d",a[i]); printf("\n");
times++;
//if (times >= 9) break;
k = min(100,n);
flag = 0;
while (k--)
{
t = rand()%n+1;
while (b[t] == times) t = rand()%n+1;
b[t] = times;
yy = a[f[t][1]]*1000+a[f[t][2]]*100+a[f[t][3]]*10+a[f[t][4]];
mm = a[f[t][5]]*10+a[f[t][6]]; dd = a[f[t][7]]*10+a[f[t][8]];
if (isdate(mm,dd,yy) == false) {flag = 1; break;}
if (isfri(mm,dd,yy) == false) {flag = 1; break;}
}
if (flag == 0)
{
num = 0;
fo(t,1,n)
{
yy = a[f[t][1]]*1000+a[f[t][2]]*100+a[f[t][3]]*10+a[f[t][4]];
mm = a[f[t][5]]*10+a[f[t][6]]; dd = a[f[t][7]]*10+a[f[t][8]];
if (isdate(mm,dd,yy) == false) {flag = 1; break;}
if (isfri(mm,dd,yy) == false) {flag = 1; break;}
num++;
}
if (flag && num > 0.75 * n && n > 100) break;
}
if (flag == 0)
{
printf("Case #%d: ",cas);
fo(i,0,9) printf("%d",a[i]); printf("\n");
break;
}
}while (next_permutation(a,a+10));
if (flag == 1)
{
printf("Case #%d: ",cas);
printf("Impossible\n");
}
}
return 0;
}
首先我对升级的价格取负,这样题意中所有的数据都会变成“奖励”
我们让这个“奖励”最大即可
枚举最小等级,然后找出每个技能升到几级最赚,这个用优先队列维护前缀和就可以了
然后需要把某一个技能压到 j j j级,那当然是赚得最少的那个变成 j j j
注意这里不是找最小的 p r e [ i ] [ k ] pre[i][k] pre[i][k],而是找最小的 p r e [ i ] [ k ] − p r e [ i ] [ j ] pre[i][k]-pre[i][j] pre[i][k]−pre[i][j],即 j j j级以上的部分赚得最少的
还有就是优先队列的前缀和要多扔一个0级,因为可以不升
#include
using namespace std;
typedef long long LL;
const int maxn = 1007;
LL c[maxn][maxn];
LL d[maxn];
struct Node{
LL cost;
int pos;
bool operator < (const Node &rhs) const
{
return cost<rhs.cost;
}
};
priority_queue<Node> q[maxn];
int main (){
int T;
scanf("%d", &T);
for (int t = 1; t <= T; t++)
{
int n, m;
scanf("%d%d", &n, &m);
for (int i=1;i<=n;i++)
while (!q[i].empty())
q[i].pop();
for (int i = 1; i<= n; i++){
for (int j = 1; j <= m; j++){
scanf("%lld", &c[i][j]);
c[i][j] = -c[i][j];
c[i][j] = c[i][j-1] +c[i][j];
q[i].push(Node{c[i][j],j});
}
q[i].push(Node{0,0});
}
for (int i=1;i<=m;i++)
scanf("%lld",&d[i]);
for (int i=1;i<=m;i++)
d[i]+=d[i-1];
LL ans = 0;
for (int j = 0; j <= m; j++){
int pos = 0;
LL tmp = 0;
LL mi = 1LL << 60;
for (int i = 1; i <= n; i++) {
while (!q[i].empty() && q[i].top().pos<j)
q[i].pop();
if (q[i].top().cost-c[i][j]< mi)
{
mi = q[i].top().cost-c[i][j], pos = i;
}
tmp+=q[i].top().cost;
}
tmp-=q[pos].top().cost;
tmp+=c[pos][j];
tmp+=d[j];
ans=max(ans,tmp);
}
printf("Case #%d: ", t);
printf("%lld\n",ans);
}
return 0;
}